诏安网站建设,做论坛网站的应用,凡科网邮箱登录,微信公众平台入口文章目录 一、线程间的通信#xff08;1#xff09;为什么要处理线程间的通信#xff08;2#xff09;等待唤醒机制 二、案例#xff08;1#xff09;案例1、创建线程2、解决线程安全问题3、等待4、唤醒5、同步监视器 #xff08;2#xff09;调用wait和notify需注意的… 文章目录 一、线程间的通信1为什么要处理线程间的通信2等待唤醒机制 二、案例1案例1、创建线程2、解决线程安全问题3、等待4、唤醒5、同步监视器 2调用wait和notify需注意的细节 三、wait与sleep的区别 一、线程间的通信
1为什么要处理线程间的通信
当我们需要多个线程来共同完成一件任务并且我们希望他们有规律的执行那么多线程之间需要一些通信机制可以协调它们的工作以此实现多线程共同操作一份数据。在同步的基础之上解决通信的问题
比如线程A用来生产包子的线程B用来吃包子的包子可以理解为同一资源线程A与线程B处理的动作一个是生产一个是消费此时B线程必须等到A线程完成后才能执行那么线程A与线程B之间就需要线程通信即—— 等待唤醒机制。
2等待唤醒机制
这是多个线程间的一种协作机制。
谈到线程我们经常想到的是线程间的竞争race比如去争夺锁但这并不是故事的全部线程间也会有协作机制。
在一个线程满足某个条件时就进入等待状态wait() / wait(time) 等待其他线程执行完他们的指定代码过后再将其唤醒notify();
或可以指定wait的时间等时间到了自动唤醒
在有多个线程进行等待时如果需要可以使用 notifyAll()来唤醒所有的等待线程。wait/notify 就是线程间的一种协作机制。
wait线程不再活动不再参与调度进入 wait set 中因此不会浪费 CPU 资源也不会去竞争锁了这时的线程状态是 WAITING 或 TIMED_WAITING。它还要等着别的线程执行一个特别的动作也即“通知notify”或者等待时间到在这个对象上等待的线程从wait set 中释放出来重新进入到调度队列ready queue中。notify则选取所通知对象的 wait set 中的一个线程释放。notifyAll则释放所通知对象的 wait set 上的全部线程。
️注意
被通知的线程被唤醒后也不一定能立即恢复执行因为它当初中断的地方是在同步块内而此刻它已经不持有锁所以它需要再次尝试去获取锁很可能面临其它线程的竞争成功后才能在当初调用 wait 方法之后的地方恢复执行。 总结如下 如果能获取锁线程就从 WAITING 状态变成 RUNNABLE可运行 状态否则线程就从 WAITING 状态又变成 BLOCKED等待锁 状态 二、案例
1案例
案例描述
使用两个线程打印 1-100。线程1, 线程2 交替打印。
分析
1、创建线程
这里使用实现的方式创建线程。
class PrintNumber implements Runnable{//共享100private int number1;Overridepublic void run() {}
}在run方法里面写上逻辑代码
class PrintNumber implements Runnable{//共享100private int number1;Overridepublic void run() {//打印数据while(true){if(number100){System.out.println(Thread.currentThread().getName():number);number;}else{break;}}}
}在main方法中创建两个线程如下
public class PrintNumberTest {public static void main(String[] args) {//创建PrintNumber类的实例PrintNumber pnew PrintNumber();Thread t1new Thread(p,线程1);Thread t2new Thread(p,线程2);t1.start();t2.start();}
}关于number的问题需要考虑同步。
在if中加一个sleep将问题放大。
目前的代码如下
public class PrintNumberTest {public static void main(String[] args) {//创建PrintNumber类的实例PrintNumber pnew PrintNumber();Thread t1new Thread(p,线程1);Thread t2new Thread(p,线程2);t1.start();t2.start();}
}class PrintNumber implements Runnable{//共享100private int number1;Overridepublic void run() {//打印数据while(true){if(number100){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName():number);number;}else{break;}}}
}输出 可以看到打印出了错误信息。 2、解决线程安全问题
现在我们使用同步代码块来解决线程安全问题。
将操作number的代码用synchronized包裹起来就是下面蓝色部分 用synchronized包裹的时候不能包裹少了那样会有安全问题。
若是包裹多了串行的场景就会更多导致效率变低。
所以操作共享数据的代码不能包裹多也不能少。
代码
package yuyi04.Communication;/*** ClassName: PrintNumberTest* Package: yuyi04.Communication* Description:* 使用两个线程打印 1-100。线程1, 线程2 交替打印。* Author 雨翼轻尘* Create 2024/2/2 0002 14:57*/
public class PrintNumberTest {public static void main(String[] args) {//创建PrintNumber类的实例PrintNumber pnew PrintNumber();Thread t1new Thread(p,线程1);Thread t2new Thread(p,线程2);t1.start();t2.start();}
}class PrintNumber implements Runnable{//共享100private int number1;Overridepublic void run() {//打印数据while(true){synchronized (this) { //当前this表示PrintNumber的实例即p,是唯一的if(number100){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName():number);number;}else{break;}}}}
}输出 可以看到现在的线程是安全的。 3、等待
紧接着需要考虑下一个问题需要将两个线程交替打印。
如何实现交替
比如线程1进入同步代码块然后打印结束出了同步代码块。
此时线程1需要进入阻塞状态让线程2进入同步代码块输出执行才能保证与线程1交替执行。
如何让线程1处于阻塞这里需要使用一个方法wait()让它处于等待状态。如下 线程一旦执行wait()方法就进入等待状态阻塞同时会释放对同步监视器的调用。
wait()方法有一个异常需要处理一下 注意sleep()不会释放同步监视器这一点需要注意。
有时候面试会问wait()与sleep()有什么区别
这就是其中一个区别sleep()不会释放对同步监视器的调用而wait()会释放对同步监视器的调用。
代码
public class PrintNumberTest {public static void main(String[] args) {//创建PrintNumber类的实例PrintNumber pnew PrintNumber();Thread t1new Thread(p,线程1);Thread t2new Thread(p,线程2);t1.start();t2.start();}
}class PrintNumber implements Runnable{//共享100private int number1;Overridepublic void run() {//打印数据while(true){synchronized (this) { //当前this表示PrintNumber的实例即p,是唯一的if(number100){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName():number);number;try {wait(); //线程一旦执行此方法就进入等待状态阻塞同时会释放对同步监视器的调用} catch (InterruptedException e) {e.printStackTrace();}}else{break;}}}}
}输出 ☕注意
一旦线程1进入同步代码块执行到wait()就会释放对同步监视器的调用然后开始等待操作。
线程2就可以拿着同步监视器进入同步代码块了当线程2执行到wait()它也开始等待。
所以两个线程都处于等待状态只能输出两个数字然后进入了阻塞状态。 4、唤醒
如何不一直等待下去呢
CtrlP可以看到wait()方法还有带参的可以设置等待时间就是达到时间自动醒来。如下 现在我们不使用这个了因为想让它们交互输出可以考虑唤醒线程。
线程1进入同步代码块执行到wait()然后释放同步监视器线程2进入同步代码块。
所以我们需要在线程2wait()之前把线程1给唤醒。
可以在这里写上notify()如下 分析一下现在的情况
线程1进入同步代码块碰到了notify()发现没有可以唤醒的线程就继续往后执行。
然后线程1打印了“1”接下来执行到wait()开始等待并且释放同步监视器。
线程2拿到锁进入同步代码块碰到了notify()发现线程1在等待就将线程1叫醒虽然叫醒了但是没有用因为已经没有同步资源了。
线程2就拿着锁继续执行即使sleep也不影响自动睡醒之后继续往后执行sleep不会导致锁被释放然后输出“2”。
当线程2执行到wait()就开始等待了同时也释放同步监视器。
此时线程1是醒着的状态从被wait()的地方继续往后执行后边要是有代码的话还需要继续执行然后拿着锁再次进入同步代码块然后碰到notify()将线程2叫醒。
代码
package yuyi04.Communication;/*** ClassName: PrintNumberTest* Package: yuyi04.Communication* Description:* 使用两个线程打印 1-100。线程1, 线程2 交替打印。* Author 雨翼轻尘* Create 2024/2/2 0002 14:57*/
public class PrintNumberTest {public static void main(String[] args) {//创建PrintNumber类的实例PrintNumber pnew PrintNumber();Thread t1new Thread(p,线程1);Thread t2new Thread(p,线程2);t1.start();t2.start();}
}class PrintNumber implements Runnable{//共享100private int number1;Overridepublic void run() {//打印数据while(true){synchronized (this) { //当前this表示PrintNumber的实例即p,是唯一的notify();if(number100){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName():number);number;try {wait(); //线程一旦执行此方法就进入等待状态阻塞同时会释放对同步监视器的调用} catch (InterruptedException e) {e.printStackTrace();}}else{break;}}}}
}输出部分 现在看到的就是交互的场景。 5、同步监视器
️关于同步监视器
这里其实省略了this如下 wait同理
notify(); //this.notify();
wait(); //this.wait();凡是在方法当中没有写是谁调用的如果是非静态方法那就少了this。 若是静态方法那就是当前类。 这里的this必须是一样的吗如下 我们先来自己定义一个同步监视器然后将obj放入 代码
public class PrintNumberTest {public static void main(String[] args) {//创建PrintNumber类的实例PrintNumber pnew PrintNumber();Thread t1new Thread(p,线程1);Thread t2new Thread(p,线程2);t1.start();t2.start();}
}class PrintNumber implements Runnable{//共享100private int number1;Object objnew Object();Overridepublic void run() {//打印数据while(true){synchronized (obj) { //obj是唯一的线程安全this.notify(); //notify();if(number100){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName():number);number;try {wait(); //线程一旦执行此方法就进入等待状态阻塞同时会释放对同步监视器的调用} catch (InterruptedException e) {e.printStackTrace();}}else{break;}}}}
}输出 会报一个异常IllegalMonitorStateException这表示当前使用的同步监视器即obj和notify()、wait()方法的调用者不一致。
所以它们的调用者必须是同步监视器。这里也可以看出notify和wait方法必须要在同步代码块或同步方法中使用只有这两个结构中才存在同步监视器Lock里面没有同步监视器 处理
既然现在的同步监视器是obj那么就用obj去调用notify和wait方法即可。
如下 代码
package yuyi04.Communication;/*** ClassName: PrintNumberTest* Package: yuyi04.Communication* Description:* 使用两个线程打印 1-100。线程1, 线程2 交替打印。* Author 雨翼轻尘* Create 2024/2/2 0002 14:57*/
public class PrintNumberTest {public static void main(String[] args) {//创建PrintNumber类的实例PrintNumber pnew PrintNumber();Thread t1new Thread(p,线程1);Thread t2new Thread(p,线程2);t1.start();t2.start();}
}class PrintNumber implements Runnable{//共享100private int number1;Object objnew Object();Overridepublic void run() {//打印数据while(true){synchronized (obj) { //obj是唯一的线程安全obj.notify(); //notify();if(number100){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName():number);number;try {obj.wait(); //线程一旦执行此方法就进入等待状态阻塞同时会释放对同步监视器的调用} catch (InterruptedException e) {e.printStackTrace();}}else{break;}}}}
}输出部分 notify和wait方法的调用者必须是“同步监视器”。 2调用wait和notify需注意的细节
上述案例涉及到三个方法的使用
wait()线程一旦执行此方法就进入等待状态。同时会释放对同步监视器的调用。notify()一旦执行此方法就会唤醒被wait()的线程中优先级最高的那一个线程。如果被wait()的多个线程的优先级相同则随机唤醒一个。被唤醒的线程从当初被wait的位置继续执行。notifyAll()一旦执行此方法就会唤醒所有被wait的线程。
️注意点
此三个方法的使用必须是在同步代码块或同步方法即synchronized结构中。(超纲Lock需要配合Condition实现线程间的通信方式更加灵活)此三个方法的调用者必须是同步监视器。否则会报IllegalMonitorStateException异常。此三个方法声明在Object类中。当初说同步监视器的时候说到“任何一个对象都可以来充当同步监视器”那么就意味着任何一个对象都应该有能力去调用这几个方法这几个方法必定是定义在Object类里面的 ☕调用wait和notify需注意的细节总结
1、wait方法与notify方法必须要由同一个锁对象调用。
因为对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
2、wait方法与notify方法是属于Object类的方法的。
因为锁对象可以是任意对象而任意对象的所属类都是继承了Object类的。
3、wait方法与notify方法必须要在同步代码块或者是同步函数中使用。
因为必须要通过锁对象调用这2个方法。否则会报java.lang.IllegalMonitorStateException异常。
三、wait与sleep的区别
wait() 和 sleep()的区别常见面试题
①相同点一旦执行都会使得当前线程结束执行状态进入阻塞状态。
②不同点
声明的位置定义方法所属的类 wait()声明在Object类中。sleep()声明在Thread类中静态的。 使用的场景不同使用范围不同 wait()只能使用在同步代码块或同步方法中。sleep()可以在任何需要使用的场景。 都在同步结构同步代码块或同步方法中使用的时候是否释放同步监视器的操作不同 wait()一旦执行会释放同步监视器。sleep()一旦执行不会释放同步监视器。 结束阻塞的方式结束等待的方式不同 wait()到达指定时间自动结束阻塞 或 无限等待直到被notify/notifyAll唤醒结束阻塞。sleep()到达指定时间自动结束阻塞。