目录
概念
什么是进程?
什么是线程呢?
为啥要有线程?
线程和进程的区别?
什么是多线程?
线程的使用
创建线程
Thread类及常见方法
启动一个线程
中断一个线程
线程合并(等待一个线程)
获得当前线程引用
线程休眠
线程礼让
线程的状态?
线程安全
线程不安全的原因
原子性?
可见性
代码重排序
线程同步的实现
synchronized使用
volatile关键字
wait和 notify
wait和sleep的异同
概念
什么是进程?
进程:进程是操作系统对一个正在运行的程序的一种抽象,换言之,可以把进程看做程序的一次运行过程;同时,在操作系统内部,进程又是操作系统进行资源分配的基本单位。不同进程中的资源是隔离的。
简单来理解,进程是计算机正在允许的一个独立应用程序,例如打开IDEA编写Java程序就是一个进程,打开浏览器查找学习资料就是一个进程。一个应用程序至少有一个进程。
什么是线程呢?
线程是组成进程的基本单位,可以完成特定的功能,一个进程由一个或者多个线程组成。每个线程就是一个 "执行流". 每个线程之间都可以按照顺序执行自己的代码. 多个线程之间 "同时" 执行着多份代码.
为啥要有线程?
1.首先, "并发编程" 成为 "刚需"。
单核 CPU 的发展遇到了瓶颈. 要想提高算力, 就需要多核 CPU.。
而并发编程能更充分利用多核 CPU 资源. 有些任务场景需要 "等待 IO", 为了让等待 IO 的时间能够去做一些其他的工作, 也需要用到并发编程.
2.其次, 虽然多进程也能实现 并发编程, 但是线程比进程更轻量.
创建线程比创建进程更快.
销毁线程比销毁进程更快.
调度线程比调度进程更快.
线程和进程的区别?
进程和线程是应用程序在执行过程中的概念,如果应用程序没有执行,比如没有打开IDEA那么就不存在线程和进程的概念。应用程序是静态的概念,进程和线程是动态的概念,有创建就有销毁,存在也是暂时的,不是永久的。
进程和线程的区别在于:进程在运行时拥有独立的内存空间,即每个进程所占用的内存都是独立的,互不干扰(不同进程中的资源是隔离的)。而多个线程是共享内存空间的,但是每个线程执行是相互独立的,同时线程必须依赖于进程才能执行,单独的线程是无法执行的,由线程控制多个进程执行。
什么是多线程?
我们通常所说的多线程是指在一个进程中,多个线程同时执行,这里说的同时执行并不是意义上的同时执行,系统会自动为每个线程分配CPU资源,在某个具体的时间段内CPU的资源被一个线程占用,在不同的时间段内由不同的线程占用CPU资源,所有多线程还是在交替执行,只不过因为CPU运行速度太快,感觉上是在同时执行。我们之前写的Java程序都是单线程,Java程序运行起来就是一个进程,该进程只有一个线程。
多线程的优势:增加运行速度
线程的使用
创建线程
创建线程的方法:
1.继承Thread类
(1)创建自定义类并继承Thread
(2)重写Thread的run()方法,并编写该线程的业务逻辑代码
2.实现Runnable接口
(1)创建自定义类并实现Runnable接口
(2)实现run()方法,并编写该线程的业务逻辑代码
(3)创建实现类的对象
(4)将此对象作为参数传递到Thread类中的构造器中,创建Thread类的对象
两种创建线程的不同:Runnable?相当于定义了线程的业务逻辑。它本身并不是线程对象。所以还需要实例化Thread对象,然后将Runnable的对象赋给Thread对象。这样Thread知道了它要完成的业务逻辑,再通过调用Thread对象的start()方法来启动该线程
/**
* 创建线程
* @Author liusifan
* @Data 2022/4/20 14:37
*/
public class MyFirstThread
{
// 继承 java.lang.Thread 类
// 光继承的情况,这个线程中一条指令都没有
// 所以我们需要给线程书写让它执行的指令(以语句的形式)
// 通过重写 run 方法
public static class A extends Thread
{
// 线程在初始化对象的时候, this代表的当前对象A,
// 然后Thread在初始化对象的时候,会给线程起一个默认的初始名,
// 所以this.getname()打印出来是“当前线程的名字:Thread-0”。
public A()
{
System.out.println(this.getName());
}
@Override
public void run()
{
System.out.println("我的第一个线程");
}
}
// 实现 java.lang.Runnable 接口
// 通过重写 run 方法,来指定任务要做的工作
static class B implements Runnable
{
@Override
public void run()
{
System.out.println("我的第一个任务");
}
}
// 主线程
public static void main(String[] args) throws InterruptedException
{
//静态内部类不需要产生外部类对象
A a=new A();
a.start();
// 创建了一个任务对象
// 把 task 作为 Thread 的构造方法传入
// 让这个新创建的 Thread 去执行 task 任务
// 语句就运行在新的线程中
// new B 类的对象,是一个 Runnable,作为任务传递给线程对象
B task=new B();
Thread b=new Thread(task);
b.start();
// b.run就是一个简单的方法调用,并不是多线程,
b.run();
}
}
对比上面两种方法:
继承 Thread 类, 直接使用 this 就表示当前线程对象的引用.
实现 Runnable 接口, this 表示的是 MyRunnable 的引用. 需要使用 Thread.currentThread()?
关于A.B两个线程:
我们加入就绪队列的时机是确定的,但什么时候被调度到CPU不确定(随机),什么时候被从CPU上调度下来不确定(随机) 但为什么现象上看起来固定? 主线程现在能创建A/B两个线程,代表现在主线程占据着CPU呢! ---- 我们的任何代码执行的前提,都是该线程拥有CPU 主线程创建两个线程+打印10次main需要的时间,远远小于时间片,所以,可以认为时间片耗尽之前,主线程可以把所有事情都干完!只有 main执行结束放弃CPU,A和B才有资格抢CPU! 大部分OS(操作系统)实现的时候会考虑公平性 ---- 先就绪的先被调度。
所以,因为a. start()先被执行,所以大概率,A线程先被拥有CPU,然后也是因为数据很少,所以就把事情都干完了再轮到B。
Thread类及常见方法
ID 是线程的唯一标识,不同线程不会重复
名称是各种调试工具用到
状态表示线程当前所处的一个情况,
优先级高的线程理论上来说更容易被调度到。
关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
是否存活,即简单的理解,为 run 方法是否运行结束了。
// 当前代码段正在被哪个线程调用的相关信息
Thread thread=Thread.currentThread();
System.out.println("线程的id:"+thread.getId());
//线程name可以改(更改线程name只是为了在后台更方便找到它),线程id是唯一的,不可以更改。
thread.setName("我是主线程");
System.out.println("线程的name:"+thread.getName());
System.out.println("线程的优先级:"+thread.getPriority());
System.out.println("线程的状态:"+thread.getState());
System.out.println("线程是否存活"+thread.isAlive());
//后台线程主要是支持工作的线程,前台线程一般是做一些交互工作的
//JVM 线程什么时候能退出?所有前台线程都退出了。
//必须是所有前台线程,和主线程没有关系,和后台线程没有关系,即使后台线程还在工作,也正常退出
//后台线程也叫守护线程(后台记录操作日志,监控内存,垃圾回收),
// 前台线程也叫用户线程,虚拟机必须确保用户线程执行完毕,虚拟机不必等待守护线程执行完毕
System.out.println("线程是否是后台线程"+thread.isDaemon());
// 将该线程设置为一个后台线程
// thread.setDaemon(true);
启动一个线程
start() 之前我们已经看到了如何通过覆写 run 方法创建一个线程对象,但线程对象被创建出来并不意味着线程就开始运行了。
- 覆写 run 方法是提供给线程要做的事情的指令清单
- 线程对象可以认为是把 李四、王五叫过来了
- 而调用 start() 方法,就是喊一声:”行动起来!“,线程才真正独立去执行了。
? ?调用 start 方法, 才真的在操作系统的底层创建出一个线程.
中断一个线程
Java 中的线程中断是一种线程间协作模式, 通过设置线程中断标志并不能直接终止该线程的执行,而是被中断的线程根据中断状态自行处理。
停止线程 – 通知 + 收取通知 + 停止
目前常见的有以下两种方式:
- 通过共享的标记来进行沟通
- 调用 interrupt() 方法来通知
示例-1: 使用自定义的变量来作为标志位.??
? ? ? ? ? 需要给标志位上加 volatile 关键字
public class ThreadDemo {
private static class MyRunnable implements Runnable {
public volatile boolean isQuit = false;
@Override
public void run() {
while (!isQuit) {
System.out.println(Thread.currentThread().getName()
+ ": 别管我,我忙着转账呢!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()
+ ": 啊!险些误了大事");
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target = new MyRunnable();
Thread thread = new Thread(target, "李四");
System.out.println(Thread.currentThread().getName()
+ ": 让李四开始转账。");
thread.start();
Thread.sleep(10 * 1000);
System.out.println(Thread.currentThread().getName()
+ ": 老板来电话了,得赶紧通知李四对方是个骗子!");
target.isQuit = true;
}
}
示例-2: 使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定义标志位.? ? ?Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记.?
public class Thread2 {
private static class MyRunnable implements Runnable {
@Override
public void run() {
// 两种方法均可以
while (!Thread.interrupted()) {
//while (!Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName()
+ ": 别管我,我忙着转账呢!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(Thread.currentThread().getName()
+ ": 有内鬼,终止交易!");
break;
}
}
System.out.println(Thread.currentThread().getName()
+ ": 啊!险些误了大事");
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target = new MyRunnable();
Thread thread = new Thread(target, "李四");
System.out.println(Thread.currentThread().getName()
+ ": 让李四开始转账。");
thread.start();
Thread.sleep(10 * 1000);
System.out.println(Thread.currentThread().getName()
+ ": 老板来电话了,得赶紧通知李四对方是个骗子!");
thread.interrupt();
}
}
通过 thread 对象调用 interrupt() 方法通知该线程停止运行 thread 收到通知的方式有两种:
- 如果线程调用了 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通知,清除中断标志
- 否则,只是内部的一个中断标志被设置,thread 可以通过
- Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志
- Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志
这种方式通知收到的更及时,即使线程正在 sleep 也可以马上收到。?
void interrupt() 方法: 中断线程,例如,当线程 A 运行时,线程 B 可以调用线程 A 的 interrupt() 方法来设置线程 A 的中断标志为 true 并立即返回。设置标志仅仅是设置标志,线程 A 实际并没有被中断,线程调用了 wait/join/sleep 等方法而阻塞挂起,这时候若线程 B 调用线程 A 的 interrupt() 方法,线程 A 会在调用这些方法的地方抛出 InterruptedException 异常而返回,清除中断标志。
线程合并(等待一个线程)
合并的意思是将指定的某个线程加入到当前线程中,合并为一个线程,两个线程交替执行变为一个线程中的两个子线程顺序执行,即一个线程执行完毕之后再来执行第二个线程,通过调用线程对象的join()方法来实现合并。
有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。例如,张三只有等李四转账成功,才决定是否存钱,这时我们需要一个方法明确等待线程的结束。
A a=new A();
B task=new B();
Thread b=new Thread(task);
b.start();
//控制其他线程,b.join 这个方法会阻塞,直到B运行结束才会返回
b.join();
//只有b结束了,a才会开始
a.start();
获得当前线程引用
public class MyFirstThread
{
public static void main(String[] args)
{
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
}
线程休眠
有一点要记得,因为线程的调度是不可控的,所以,这个方法只能保证休眠时间是大于等于参数设置的休眠时间。
public class ThreadDemo
{
public static void main(String[] args) throws InterruptedException {
System.out.println(System.currentTimeMillis());
Thread.sleep(3000);
System.out.println(System.currentTimeMillis());
}
}
线程礼让
?当一个线程调用 yield 方法时, 实际就是在暗示线程调度器当前线程请求让出自己的 CPU 使用, 但是线程调度器可以无条件忽略这个暗示。 我们知道操作系统是为每个线程分配一个时间片来占用 CPU 的, 正常情况下当一个线程把分配给自己的时间片使用完后, 线程调度器才会进行下一轮的线程调度, 而当一个线程调用了 Thread 类的静态方法 yield 时, 是在告诉线程调度器自己占有的时间片中还没有使用完的部分自己不想使用了, 这暗示线程调度器现在就可以进行下一轮的线程调度。
当一个线程调用 yield 方法时,当前线程会让出 CPU 使用权, 然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个线程优先级最高的线程,当然也有可能会调度到刚刚让出 CPU 的那个线程来获取 CPU 执行权。?
/**
* 线程礼让
* 礼让线程,让当前正在执行的线程暂停,但不阻塞
* 将线程从运行状态转为就绪状态
* 让cpu重新调度,礼让不一定成功(cpu调度是随机的)
* @Author liusifan
* @Data 2022/4/17 16:29
*/
public class ThreadYield
{
static class PrintWhoAmI extends Thread
{
private final String who;
public PrintWhoAmI(String who)
{
this.who = who;
}
@Override
public void run()
{
for (int i=0;i<500;i++)
{
System.out.println("我是 " + who);
if (who.equals("张三"))
{
//yield让出CPU,虽然让出cpu,但是还会出现
Thread.yield();
}
}
}
}
public static void main(String[] args)
{
PrintWhoAmI a = new PrintWhoAmI("张三");
PrintWhoAmI b = new PrintWhoAmI("李四");
a.start();
b.start();
}
}
?可以看见,虽然张三让出了CPU,但是还会有张三打印出来。礼让不一定成功。
线程的状态?
- NEW: 安排了工作, 还未开始行动
- RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作.(线程对象创建后,其他线程(比如main线程) 调用了该线程的 start() 方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取 cpu 的使用权。)
- BLOCKED: 这几个都表示排队等着其他事情(阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了 cpu 时间片,暂时停止运行。知道线程进入可运行(runnable)状态,才有机会再次获得cpu时间片转到运行(running)状态。)
- WAITING: 这几个都表示排队等着其他事情(线程调用 wait() 方法之后,就会从 RUNNABLE 状态变为 WAITING 无时限等待状态)
- TIMED_WAITING: 这几个都表示排队等着其他事情
- TERMINATED: 工作完成了.
举个例子:
线程安全
想给出一个线程安全的确切定义是复杂的,但我们可以这样认为: 如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。(即就是 100% 正确)
什么是线程安全问题呢? 线程安全问题是指,多个线程对同一个共享数据进行操作时,线程没来得及更新共享数据,从而导致另外线程没得到最新的数据,从而产生线程安全问题。
举个例子:
线程不安全的原因
- 原子性
- 内存可见性
- 代码重排序
原子性?
什么是原子性
我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入房间之后,还没有出来;B 是不是也可以进入房间,打断 A 在房间里的隐私。这个就是不具备原子性的。
那我们应该如何解决这个问题呢?
是不是只要给房间加一把锁,A 进去就把门锁上,其他人是不是就进不来了。这样就保证了这段代码的原子性了。有时也把这个现象叫做同步互斥,表示操作是互相排斥的
一条 java 语句不一定是原子的,也不一定只是一条指令 比如我们看到的 i++,其实是由三步操作组成的:
- 从内存把数据读到 CPU
- 进行数据更新
- 把数据写回到 CPU
不保证原子性会给多线程带来什么问题?
如果一个线程正在对一个变量操作,中途其他线程插入进来了,如果这个操作被打断了,结果就可能是错误的。
大部分线程不安全都是因为原子性(占百分之90以上)
可见性
可见性指, 一个线程对共享变量值的修改,能够及时地被其他线程看到.?
Java 内存模型 (JMM): Java虚拟机规范中定义了Java内存模型. 目的是屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果.
- 线程之间的共享变量存在 主内存 (Main Memory).
- 每一个线程都有自己的 "工作内存" (Working Memory) .
- 当线程要读取一个共享变量的时候, 会先把变量从主内存拷贝到工作内存, 再从工作内存读取数据.
- 当线程要修改一个共享变量的时候, 也会先修改工作内存中的副本, 再同步回主内存.?
代码重排序
什么是代码重排序
一段代码是这样的:
- 去前台取下 U 盘
- 去教室写 10 分钟作业
- 去前台取下快递
如果是在单线程情况下,JVM、CPU指令集会对其进行优化,比如,按 1->3->2的方式执行,也是没问题,可以少跑一次前台。这种叫做指令重排序。
为什么要代码重排序 很多时候,重排序后的执行效率更高。
代码重排序是有基本要求的 单线程情况下,重排序后的结果得和重排序之前效果一致。
线程同步的实现
可以通过synchronized修饰方法来实现线程同步,每个Java对象都有一个内置锁,内置锁会保护使用synchronized关键字修饰的方法,要调用方法必须先使用内置锁,否则就会处于阻塞状态。
使用同步方法,对方法进行synchronized关键字修饰 将同步代码块提取出来成为一个方法,用synchronized关键字修饰此方法。 对于runnable接口实现多线程,只需要将同步方法用synchronized修饰 而对于继承自Thread方式,需要将同步方法用static和synchronized修饰,因为对象不唯一(锁不唯一)
Runable天生共享锁,而Thread中需要用static对象或者this关键字或者当前类(class)来充当唯一锁。
synchronized使用
synchronized特性
1.互斥性
synchronized 会起到互斥效果, 某个线程执行到某个对象的 。synchronized 中时, 其他线程如果也执行到同一个对象 synchronized 就会阻塞等待.
- 进入 synchronized 修饰的代码块, 相当于加锁
- 退出 synchronized 修饰的代码块, 相当于解锁
2.刷新内存(内存不可见问题)
synchronized 的?作过程:
- 获得互斥锁
- 从主内存拷?变量的最新副本到?作的内存
- 执?代码
- 将更改后的共享变量的值刷新到主内存
- 释放互斥锁
3.可重入
synchronized 同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题。
1.直接修饰普通方法: 锁的 SynchronizedDemo 对象
public class SynchronizedDemo
{ ?
?public synchronized void methond() { ?
}
}
2. 修饰静态方法: 锁的 SynchronizedDemo 类的对象(static视为对类加锁)
public class SynchronizedDemo
{
? ?public synchronized static void method() {
}
}
3.修饰代码块: 明确指定锁哪个对象. (锁当前对象)
public class SynchronizedDemo
{ ? ?
public void method()
{ ? ? ? ?
synchronized (this) {
}
}
}
4.锁类对象?
public class SynchronizedDemo
{ ? ?
public void method()
{ ? ? ?
?synchronized (SynchronizedDemo.class) {
? ? ?}
}
我们重点要理解,synchronized 锁的是什么. 两个线程竞争同一把锁, 才会产生阻塞等待.
两个线程分别尝试获取两把不同的锁, 不会产生竞争.?
当锁定的是一个实例方法时,加锁效果并没有实现,因为每一个线程都有这样的一个实例方法,相互之间是独立的。即一个方法并不是所有线程共享的,实际运行的情况是每个线程都有自己的一把锁,然后并行访问,所以给实例方法添加synchronized关键字并不能实现线程之间的同步,线程同步的本质是锁定多个线程所共享的资源。
?synchronized加锁操作的作用:
- 原子性 (通过将需要保证原子性的操作互斥起来)(绝大部分作用)
- 内存可见性
- 代码重排序
volatile关键字
volatile修饰的变量能保证内存可见性(最主要的作用)
被 volatile 修饰的变量
- 1.任何的读取,必须从主内存中读取
- 2.任何的修改,必须同步回主内存中去
永远保证主内存的值是最新的,并且每次任何时候都是读最新的值。
前面我们讨论内存可见性时说了, 直接访问工作内存(实际是 CPU 的寄存器或者 CPU 的缓存), 速度非常快, 但是可能出现数据不一致的情况。
加上 volatile , 强制读写内存. 速度是慢了, 但是数据变的更准确了.。
volatile能解决原子性问题
volatile保证原子性体现在:double / long 的赋值中的原子性
这些都不是原子的:long a = 1L;? ?double d = 1.0;?
加上 volatile 就是原子的: volatile long a = 1L;? volatile double d = 1.0;
这是因为,JVM的基本操作单位32位的,而long和double都是64位的,不是原子的。?
volatile约束代码重排序
volatile Object o = new Object(); 被 volatile 修饰,仅仅实例化的时候不能被重排序。
?举个例子:
wait和 notify
wait()和notify()都属于Object类的,java中的对象都带有这两个方法。
要使用wait()和notify()都必须首先对“对象” 进行 synchronized 加锁,脱离 synchronized 使用 wait()和notify()?会直接抛出异常.?
wait()方法:
wait 做的事情:
- 使当前执行代码的线程进行等待. (把线程放到等待队列中)
- 释放当前的锁
- 满足一定条件时被唤醒, 重新尝试获取这个锁.?
wait 结束等待的条件:
- 其他线程调用该对象的 notify 方法.
- wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间).
- 其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常.
notify()方法:
notify 方法是唤醒等待的线程.?
notify唤醒规则:
- 随机唤醒,如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 "先来后到")
- wait-notify没有保存状态(换言之,先notify后wait,wait无法感知到前面有notify,wait会永远等下去)
import thread.Thread_demo.Lock;
import java.util.concurrent.TimeUnit;
/**
* wait和notify
* @Author liusifan
* @Data 2022/4/24 15:00
*/
public class Demo1
{
static Object lock =new Object();
static class MyThread extends Thread {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock)
{
// 其他线程调用该对象的 notify 方法.
System.out.println("唤醒主线程");
lock.notify();
}
}
}
public static void main(String[] args) throws InterruptedException
{
//保证主线程先执行
synchronized (lock)
{
MyThread t =new MyThread();
t.start();
// 使当前执行代码的线程进行等待. (把线程放到等待队列中)
// 释放当前的锁
// 满足一定条件时被唤醒, 重新尝试获取这个锁.
// 再加锁
lock.wait();
System.out.println("主线程已唤醒");
}
}
}
notifyAll()方法:
notify方法只是唤醒某一个等待线程. 使用notifyAll方法可以一次唤醒所有的等待线程.??
package thread.Thread_demo.wait_notify;
import java.util.concurrent.TimeUnit;
/**
* 唤醒所有线程
* @Author liusifan
* @Data 2022/4/25 20:37
*/
public class Demo2
{
static Object o = new Object();
static class MyThread extends Thread
{
@Override
public void run()
{
synchronized (o)
{
try
{
o.wait();
System.out.println(getName());
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException
{
// 创建十个线程
for (int i = 0; i < 10; i++) {
MyThread t = new MyThread();
t.start();
}
// 保证了子线程们先 wait,主线程就先休眠一会儿
TimeUnit.SECONDS.sleep(2);
synchronized (o)
{
// 随机唤醒一个线程
// o.notify();
// 唤醒全部线程
o.notifyAll();
}
}
}
wait和sleep的异同
相同点:
- 一旦执行方法以后,都会使得当前的进程进入阻塞状态
- 都可以相应一个 Interrupt() 中断请求。
?不同点:
- 1.两个方法声明的位置不同,Thread类中声明sleep,Object类中声明wait。
- 调用的要求不同,sleep可以在任何需要的场景下调用,wait必须使用在同步代码块或者同步方法中(wait 必须配合 synchronized 使用,而 sleep 不需要)
- 如果两个方法都使用在同步代码块或同步方法中,sleep不会释放锁,wait会释放锁
- sleep 必须要传递一个数值类型的参数;而 wait 可以不传参。
- sleep 让线程进入到 TIME_WAITING 状态;而无参的 wait 方法让线程进入了 WAITING 状态
- 一般情况下 sleep 只能等待超时时间之后再恢复执行;而 wait 可以接收 notify/notifyAll 之后继续执行。
|