什么是线程?线程概念及方法详细讲解
目录
1 多线程
1.1 并发与并⾏
1.2 线程与进程
1.3 创建线程类
2 多线程详解
2.1 多线程原理
2.2 Thread类
2.3 创建线程⽅式⼆
2.4 Thread 和 Runnable 的区别
2.5 匿名内部类⽅式实现线程的创建
3 线程安全
3.1 线程安全
3.2 线程同步
3.3 同步代码块
3.4 同步⽅法
3.5 Lock锁
4 线程状态
4.1 线程状态概述
4.2 Timed Waiting(计时等待)
4.3 BLOCKED(锁阻塞)
4.4 Waiting(⽆限等待)
4.5 练习
5 小结
1 多线程
1.1 并发与并⾏
- 并发:指两个或多个事件在同⼀个时间段内发⽣。
- 并⾏:指两个或多个事件在同⼀时刻发⽣(同时发⽣)。
注意:单核处理器的计算机肯定是不能并⾏的处理多个任务的,只能是多个任务在单个 CPU 上并发运 ⾏。同理 , 线程也是⼀样的,从宏观⻆度上理解线程是并⾏运⾏的,但是从微观⻆度上分析却是串⾏ 运⾏的,即⼀个线程⼀个线程的去运⾏,当系统只有⼀个 CPU 时,线程会以某种顺序执⾏多个线程, 我们把这种情况称之为线程调度。
1.2 线程与进程
- 进程:是指⼀个内存中运⾏的应⽤程序,每个进程都有⼀个独⽴的内存空间,⼀个应⽤程序可以同时运⾏多个进程;进程也是程序的⼀次执⾏过程,是系统运⾏程序的基本单位;系统运⾏⼀个程序即是 ⼀个进程从创建、运⾏到消亡的过程。
- 线程:线程是进程中的⼀个执⾏单元,负责当前进程中程序的执⾏,⼀个进程中⾄少有⼀个线程。⼀个进程中是可以有多个线程的,这个应⽤程序也可以称之为多线程程序。
![](/assets/blank.gif)
- 分时调度
所有线程轮流使⽤ CPU 的使⽤权,平均分配每个线程占⽤ CPU 的时间。
- 抢占式调度
优先让优先级⾼的线程使⽤ CPU,如果线程的优先级相同,那么会随机选择⼀个(线程随机性), Java使⽤的为抢占式调度。
- 设置线程的优先级
2.抢占式调度详解
1.3 创建线程类
public class Demo01 {public static void main(String[] args) {// 创建⾃定义线程对象MyThread mt = new MyThread("新的线程!");// 开启新线程mt.start();// 在主⽅法中执⾏for循环for (int i = 0; i < 10; i++) {System.out.println("main线程!" + i);}}
}
public class MyThread extends Thread {// 定义指定线程名称的构造⽅法public MyThread(String name) {// 调⽤⽗类的String参数的构造⽅法,指定线程的名称super(name);}/*** 重写run⽅法,完成该线程执⾏的逻辑*/@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(getName() + ":正在执⾏!" + i);}}
}
2 多线程详解
2.1 多线程原理
public class MyThread extends Thread {/** 利⽤继承中的特点* 将线程名称传递 进⾏设置*/public MyThread(String name) {super(name);}/** 重写run⽅法* 定义线程要执⾏的代码*/public void run() {for (int i = 0; i < 20; i++) {//getName()⽅法 来⾃⽗亲System.out.println(getName() + i);}}
}
public class Demo {public static void main(String[] args) {System.out.println("这⾥是main线程");MyThread mt = new MyThread("⼩强");mt.start(); // 开启了⼀个新的线程for (int i = 0; i < 20; i++) {System.out.println("旺财:" + i);}}
}
![](/assets/blank.gif)
![](/assets/blank.gif)
2.2 Thread类
- public Thread() :分配⼀个新的线程对象。
- public Thread(String name) :分配⼀个指定名字的新的线程对象。
- public Thread(Runnable target) :分配⼀个带有指定⽬标新的线程对象。
- public Thread(Runnable target, String name) :分配⼀个带有指定⽬标新的线程对象并指定名字。
- public String getName() :获取当前线程名称。
- public void start() :导致此线程开始执⾏;Java虚拟机调⽤此线程的run⽅法。
- public void run() :此线程要执⾏的任务在此处定义代码。
- public static void sleep(long millis) :使当前正在执⾏的线程以指定的毫秒数暂停(暂时停⽌执⾏)。
- public static Thread currentThread() :返回对当前正在执⾏的线程对象的引⽤。
2.3 创建线程⽅式⼆
public class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 0; i < 20; i++) {System.out.println(Thread.currentThread().getName() + " " + i);}}
}
public class Demo {public static void main(String[] args) {// 创建⾃定义类对象 线程任务对象MyRunnable mr = new MyRunnable();// 创建线程对象Thread t = new Thread(mr, "⼩强");t.start();for (int i = 0; i < 20; i++) {System.out.println("旺财 " + i);}}
}
Tips : Runnable 对象仅仅作为 Thread 对象的 target , Runnable 实现类⾥包含的 run() ⽅法仅作为线程 执⾏体。⽽实际的线程对象依然是 Thread 实例,只是该 Thread 线程负责执⾏其 target 的 run() ⽅法。
2.4 Thread 和 Runnable 的区别
扩充:在 java 中,每次程序运⾏⾄少启动 2 个线程。⼀个是 main 线程,⼀个是垃圾收集(GC)线程。因为 每当使⽤ java 命令执⾏⼀个类的时候,实际上都会启动⼀个 JVM ,每⼀个 JVM 其实在就是在操作系 统中启动了⼀个进程。
2.5 匿名内部类⽅式实现线程的创建
public class NoNameInnerClassThread {public static void main(String[] args) {
// new Runnable() {
// public void run() {
// for (int i = 0; i < 20; i++) {
// System.out.println("张宇:" + i);
// }
// }
// }; //---这个整体 相当于new MyRunnable()Runnable r = new Runnable() {public void run() {for (int i = 0; i < 20; i++) {System.out.println("张宇:" + i);}}};new Thread(r).start();for (int i = 0; i < 20; i++) {System.out.println("费⽟清:" + i);}}
}
3 线程安全
3.1 线程安全
public class Ticket implements Runnable {private int ticket = 100;/** 执⾏卖票操作*/@Overridepublic void run() {// 每个窗⼝卖票的操作// 窗⼝ 永远开启while (true) {if (ticket > 0) {// 有票 可以卖// 出票操作// 使⽤sleep模拟⼀下出票时间try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}// 获取当前线程对象的名字String name = Thread.currentThread().getName();System.out.println(name + "正在卖:" + ticket--);}}}
}
public class Demo {public static void main(String[] args) {// 创建线程任务对象Ticket ticket = new Ticket();// 创建三个窗⼝对象Thread t1 = new Thread(ticket, "窗⼝1");Thread t2 = new Thread(ticket, "窗⼝2");Thread t3 = new Thread(ticket, "窗⼝3");// 同时卖票t1.start();t2.start();t3.start();}
}
结果中有⼀部分这样现象:
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,⽽⽆写操作,⼀般来说,这个全局变量是线程安全的;若有多个线程同时执⾏写操作,⼀般都需要考 虑线程同步,否则的话就可能影响线程安全。
3.2 线程同步
窗⼝1线程进⼊操作的时候,窗⼝2和窗⼝3线程只能在外等着,窗⼝1操作结束,窗⼝1和窗⼝2和窗⼝3才有机会进⼊代码去执⾏。也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。
3.3 同步代码块
- 同步代码块: synchronized 关键字可以⽤于⽅法中的某个区块中,表示只对这个区块的资源实⾏互斥访问。
synchronized(同步锁) {需要同步操作的代码
}
注意:在任何时候,最多允许⼀个线程拥有同步锁,谁拿到锁就进⼊代码块,其他的线程只能在外等 着( BLOCKED )。
public class Ticket implements Runnable {private int ticket = 100;Object lock = new Object();/** 执⾏卖票操作*/@Overridepublic void run() {// 每个窗⼝卖票的操作// 窗⼝ 永远开启while (true) {synchronized (lock) {if (ticket > 0) { // 有票 可以卖// 出票操作// 使⽤sleep模拟⼀下出票时间try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}// 获取当前线程对象的名字String name = Thread.currentThread().getName();System.out.println(name + "正在卖: " + ticket--);}}}}
}
3.4 同步⽅法
- 同步⽅法:使⽤ synchronized 修饰的⽅法,就叫做同步⽅法,保证A线程执⾏该⽅法的时候,其他线程只能在⽅法外等着。
public synchronized void method() {可能会产⽣线程安全问题的代码
}
同步锁是谁?对于⾮ static ⽅法,同步锁就是 this 。对于 static ⽅法,我们使⽤当前⽅法所在类的字节码对象(类名 .class )。
使⽤同步⽅法代码如下:
public class Ticket implements Runnable {private int ticket = 100;/** 执⾏卖票操作*/@Overridepublic void run() {// 每个窗⼝卖票的操作// 窗⼝ 永远开启while (true) {sellTicket();}}/** 锁对象 是 谁调⽤这个⽅法 就是谁* 隐含 锁对象 就是 this*/public synchronized void sellTicket() {if (ticket > 0) { // 有票 可以卖// 出票操作// 使⽤sleep模拟⼀下出票时间
try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}// 获取当前线程对象的名字String name = Thread.currentThread().getName();System.out.println(name + "正在卖:" + ticket--);}}
}
3.5 Lock锁
- public void lock() :加同步锁。
- public void unlock() :释放同步锁。
public class Ticket implements Runnable {private int ticket = 100;Lock lock = new ReentrantLock();/** 执⾏卖票操作*/@Overridepublic void run() {// 每个窗⼝卖票的操作// 窗⼝ 永远开启while (true) {lock.lock();if (ticket > 0) { // 有票 可以卖// 出票操作// 使⽤sleep模拟⼀下出票时间try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}// 获取当前线程对象的名字String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket--);}lock.unlock();}}
}
4 线程状态
4.1 线程状态概述
![](/assets/blank.gif)
4.2 Timed Waiting(计时等待)
public class MyThread extends Thread {public void run() {for (int i = 0; i < 100; i++) {if ((i) % 10 == 0) {System.out.println("-------" + i);}System.out.print(i);try {Thread.sleep(1000);System.out.print(" 线程睡眠1秒!\n");} catch (InterruptedException e) {e.printStackTrace(); }}}public static void main(String[] args) {new MyThread().start();}
}
⼩提示: sleep() 中指定的时间是线程不会运⾏的最短时间。因此, sleep() ⽅法不能保证该线程睡眠到期后就开始⽴刻执⾏。
Timed Waiting 线程状态图:
4.3 BLOCKED(锁阻塞)
![](/assets/blank.gif)
4.4 Waiting(⽆限等待)
public class WaitingTest {public static Object obj = new Object();public static void main(String[] args) {// 演示waitingnew Thread(new Runnable() {@Overridepublic void run() {while (true) {synchronized (obj) {try {System.out.println(Thread.currentThread().getName() + "=== 获取到锁对
象,调⽤wait⽅法,进⼊waiting状态,释放锁对象");obj.wait(); // ⽆限等待// obj.wait(5000); // 计时等待, 5秒 时间到,⾃动醒来} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "=== 从waiting状态
醒来,获取到锁对象,继续执⾏了");}}}}, "等待线程").start();new Thread(new Runnable() {@Overridepublic void run() {
// while (true) { // 每隔3秒 唤醒⼀次try {System.out.println(Thread.currentThread().getName() + "----- 等待3秒 钟");Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (obj) {System.out.println(Thread.currentThread().getName() + "----- 获取到锁对
象,调⽤notify⽅法,释放锁对象");obj.notify();}}
// }}, "唤醒线程").start();}
}
![](/assets/blank.gif)
⼀条有意思的 Tips :我们在翻阅 API 的时候会发现 Timed Waiting (计时等待)与 Waiting (⽆限等待)状态联系还是很紧密的,⽐如 Waiting (⽆限等待)状态中 wait ⽅法是空参的,⽽ Timed Waiting (计时等待)中 wait ⽅ 法是带参的。这种带参的⽅法,其实是⼀种倒计时操作,相当于我们⽣活中的⼩闹钟,我们设定好时 间,到时通知,可是如果提前得到(唤醒)通知,那么设定好时间再通知也就显得多此⼀举了,那么 这种设计⽅案其实是⼀举两得。如果没有得到(唤醒)通知,那么线程就处于 Timed Waiting 状态, 直到倒计时完毕⾃动醒来;如果在倒计时期间得到(唤醒)通知,那么线程从 Timed Waiting 状态⽴刻唤醒。
4.5 练习
线程通信:练习1
需求:两个线程
线程1:图片的加载 1%~100%
线程2:图片的显示 显示
同时开启线程
线程2 等待 线程1 结束后在执行,
在线程2中使用 线程1.join() -> 线程2 进入阻塞状态
采用匿名内部类的方式
public class ThreadDemo3 {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(){public void run(){System.out.println("开始加载...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}for (int i = 1; i <= 100; i++) {System.out.println("已加载" + i + "%");try {Thread.sleep((long) (Math.random() * 1000));} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("结束加载");}};Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {try {t1.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("等待加载中。。。");System.out.println("显示图片");}});t1.start();t2.start();}
}
线程通信:练习2
需求: 两个线程
线程1: 图片的加载 1% ~ 100% (等待图片显示) 图片的下载 1%~100%
线程2: 图片的显示
同时开启线程: 显示之前 需要等待 加载完成
显示后 才能开始下载
wait(long): 等待指定的时间毫秒值
wait(): 无限等待, 可以被唤醒 notify() notifyAll()
public class TreadDemo4 {public static void main(String[] args) {Object obj = new Object();//这是用来做加锁的工具的, 此案例中什么意义都没有LoadThread load = new LoadThread(obj);ShowThread showT = new ShowThread(obj);Thread show = new Thread(showT);load.start();show.start();}
}class LoadThread extends Thread {private Object obj;public LoadThread(Object obj) {this.obj = obj;}public void run() {System.out.println("开始加载...");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}for (int i = 1; i <= 100; i++) {System.out.println("已加载" + i + "%");try {Thread.sleep((long) (Math.random() * 300));} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("结束加载");synchronized (obj){obj.notify();}synchronized (obj){try {obj.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("开始下载...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}for (int i = 1; i <= 100; i++) {System.out.println("已下载" + i + "%");try {Thread.sleep((long) (Math.random() * 300));} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("结束下载");}
}class ShowThread implements Runnable {private Object obj;public ShowThread(Object obj) {this.obj = obj;}@Overridepublic void run() {
// try {
// t1.join();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }System.out.println("等待加载中。。。");synchronized (obj) {try {obj.wait();} catch (InterruptedException e) {e.printStackTrace();}}try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("显示图片");synchronized (obj){obj.notify();}}
}
5 小结
多线程: 父类 Thread
程序: 软件, 工程
进程: 正在运行的程序
线程: 进程中的任务单位CPU 可以"同时"处理多个线程
并行: 同一时刻, 同时运行, 通常需要多核处理器
并发: 多线程, 交替执行(交替速度足够快, 看起来是同时)实现多线程:
main -> 一个线程
执行多线程: 随机性方式一: 只能继承一个类, 功能性单一
1.自定义类, 继承Thread
2.重写run方法
3.在主程序中创建线程对象
4.开启线程 start()方式二: 实现接口
1.自定义类, 实现Runnable接口
2.实现run方法
3.创建线程对象 ※ 使用Runnable对象来构造
4.开启线程 start方式三: 匿名内部类
Thread 基础的API:
String getName(): Thread 属性 name
static Thread currentThread(): 获得当前线程对象
static void sleep(long time): 当前线程的阻塞时间线程的状态:
见图线程安全: 多个线程共享资源
解决安全: 实现线程同步
加锁: 同步锁 synchronized, 需要借助一个对象
Lock锁 接口 实现类 ReentrantLock()
上锁 lock() 解锁 unlock()线程其他属性和方法:
setPriority(1-10越来越大): 设置优先级, 提升了这个线程的执行概率
setDaemon(true): 设置守护线程, 所有的"前置"线程结束, 守护线程也将自动结束
GC -> 垃圾回收(守护线程)
System.gc() -> 手动清理
什么是线程?线程概念及方法详细讲解相关推荐
- Require使用方法详细讲解
Require使用方法详细讲解 文章目录 Require使用方法详细讲解 一.AMD 规范 1,AMD 基本介绍 2,AMD 模块规范 二.RequireJS 介绍 1,什么是 RequireJS 2 ...
- Python海龟Turtle的使用画中秋画的方法详细讲解
最近发现很多博主使用Turtle库进行画作,今天来详细讲解海龟库方法 一.定义: Python的turtle库是一个用于绘制图形的库,它来自 Wally Feurzeig, Seymour Paper ...
- String类中方法详细讲解
学习目标: String类方法详细讲解 学习内容: 1.value属性 了解String类的value属性: private final char value[] ; //String类的不可变特性就 ...
- 队列使用方法详细讲解
队列详细讲解 队列是一种先进先出的线性表,它只允许在表的一段进行插入元素,在表的另一端删除元素,先进先出,插入的一端叫做队尾(back),删除的一段叫做对头(front) 队列定义在<queun ...
- Java split方法详细讲解
今天是圣诞节,我是中国人,无视圣诞节. 文章可能有点长,看下来必定有所收获. 没有学过正则表达式的去b站看,一个半小时应该可以看完,要看请点这里 这是必备的前置技能,不懂得话没法真正明白split用法 ...
- Android --- PagerAdapter的使用方法详细讲解
PagerAdapter简介 PagerAdapter是android.support.v4包中的类,它的子类有FragmentPagerAdapter, FragmentStatePagerAdap ...
- matlab蒙特卡罗方法求体积_蒙特卡罗方法详细讲解与MATLAB实现.ppt
下面给出几个常用的α与的数值: ? 关于蒙特卡罗方法的误差需说明两点:第一,蒙特卡罗方法的误差为概率误差,这与其他数值计算方法是有区别的.第二,误差中的均方差σ是未知的,必须使用其估计值 来代替,在计 ...
- 泰拉瑞亚修改器服务器能用吗,泰拉瑞亚修改器使用方法详细讲解
泰拉瑞亚修改器的使用方法一直是小伙伴们所关心的问题,这款游戏走的是像素风,依旧是沙盒内游戏专用的风格,自由度非常的高,而且这款游戏目前在PC端和移动端上都可以使用了,在PC上玩家可以使用修改器,而在移 ...
- javascript中常用数组方法详细讲解
javascript中数组常用方法总结 1.join()方法: Array.join()方法将数组中所以元素都转化为字符串链接在一起,返回最后生成的字符串.也可以指定可选的字符串在生成的字符串中来分隔 ...
最新文章
- ASP.NET配置文件Web.config
- python opencv生成 html5 支持的mp4
- C#windows服务开发
- RHEL 5.4 安装Oracle 11gR2, 准备篇...
- 室内使用酒精消毒的时候一定要注意开窗!!!
- 用友政务知识管理平台_云创数字政务大数据平台,助力政务工作高效管理
- python调用摄像头录制视频_Python OpenCV使用摄像头捕获视频
- Windows中下载并安装RabbitMQ
- 对称密钥、非对称密钥、数字签名、数字证书
- HTML|按钮和多选框
- 2021-11-13 变电站综合自动化 二次系统安全
- (附源码)springboot嘉应房地产公司质量管理系统 毕业设计 453100
- 电子设计(1)二极管防电源反接电路
- 阿里云 杭州 ARM 云服务器性能评测
- ch341a i2c 安卓_CH341A实现USB转I2C的问题
- 思科ccie和华为hcie中交换机环路的产生原因和解决方法
- 使用Fiddler对手机App抓包
- 新媒体工作者必备常识
- css:html元素的定位
- shell脚本 查看还有多少天过生日