网站自动更新,公司网站首页怎么设置,网站开发流程的认识,龙岩天宫山住宿目录 〇、先总结一下这三个方法带来的Java线程状态变化 一、obj.wait() 1.1 作用 1.2 使用前需要持有线程共享对象的锁 1.3 使用技巧 二、obj.notify(All)() 1.1 notify() 方法 1.1.1 调用notify()或notifyAll()不会释放线程的锁 1.2 notifyAll() 方法 1.3 使用技巧 三、使用实… 目录 〇、先总结一下这三个方法带来的Java线程状态变化 一、obj.wait() 1.1 作用 1.2 使用前需要持有线程共享对象的锁 1.3 使用技巧 二、obj.notify(All)() 1.1 notify() 方法 1.1.1 调用notify()或notifyAll()不会释放线程的锁 1.2 notifyAll() 方法 1.3 使用技巧 三、使用实例 四、wait()/notify()/notifyAll() 为什么定义在 Object 类中 wait()、notify/notifyAll() 方法都是Object的本地final方法无法被重写。
〇、先总结一下这三个方法带来的Java线程状态变化
当Java线程调用wait()方法后该线程会进入等待队列并且会释放占用的锁资源。线程状态会变为WAITING或TIMED_WAITING。该线程不会被挂起到外存而是在内存中等待被唤醒。线程等待的条件通常是由其他线程调用notify()或notifyAll()方法来唤醒该线程。
当线程被唤醒时它会重新尝试获取锁资源并从wait()方法返回。线程状态会变为BLOCKED直到它获得了锁资源为止。如果成功获取锁资源线程状态会变为RUNNABLE然后可以继续执行。如果获取锁资源失败则线程会继续等待并且状态会维持在BLOCKED或WAITING或TIMED_WAITING状态直到它再次被唤醒。
需要注意的是线程在等待期间会消耗一定的资源因此应该避免过多的线程等待。另外线程在等待期间不会占用CPU时间片因此可以减少CPU的利用率提高系统的性能。
一、obj.wait()
1.1 作用
wait()是Object里面的方法Object是所有对象的父类即所有对象都可以调用wait()方法。wait方法还有可以传入等待时长的可以让线程等待指定的时间后自动被唤醒。调用wait()会使Java线程进入到WAITING状态调用wait(long time)会使Java线程进入到TIMED_WAITING状态。WAITING和TIMED_WAITING状态就是阻塞状态
当一个线程调用一个共享变量的wait()方法时该线程会阻塞等待。直到发生以下几种情况才会恢复执行
其他线程调用了该共享对象的 notify() 方法或者 notifyAll() 方法继续往下走其他线程调用了该线程的 interrupt() 方法该线程会 InterruptedException 异常返回
等待线程假设调用的是obj对象的wait()方法wait的执行线程也就是被暂停的线程就称为对象obj上的等待线程。对象的wait方法可能被不同的线程执行所以同一个对象可能会有多个等待线程。
1.2 使用前需要持有线程共享对象的锁
在使用wait()、notify()和notifyAll()方法方法前需要先持有锁。如果调用线程共享对象的wait()、notify()和notifyAll()方法的线程没有事先获取该对象的监视器锁调用线程会抛出IllegalMonitorStateException 异常。当线程调用wait() 之后就会释放该对象的监视器锁。
使用wait()、notify()和notifyAll()方法方法前需要先持有锁
表象wait、notify(ALL)方法需要调用 monitor 对象本质Java的线程通信实质上是共享内存而不是直接通信
那么一个线程如何才能获取一个共享变量的监视器锁
1、执行synchronized 同步代码块使用该共享变量作为参数。
synchronized(共享变量) {// TODO
}
2、调用该共享变量的同步方法synchronized 修饰
synchronized void sum(int a, int b) {// TODO
} 如下代码示例线程A与线程B在线程A中调用共享变量obj的wait()方法在线程B中进行唤醒notify()。
/*** Object的Wati()方法的使用*/
Slf4j
public class WaitTest {public static void main(String[] args) {// 定义一个共享变量Object obj new Object();// 创建线程AThread threadA new Thread(new Runnable() {Overridepublic void run() {log.info(线程 Thread.currentThread().getName()开始执行);try {// 获取共享变量的对象锁synchronized(obj){// 线程A 等待log.info(线程 Thread.currentThread().getName()等待);// 调用wait()线程A阻塞并且释放掉获取到的obj的对象锁obj.wait();}} catch (InterruptedException e) {e.printStackTrace();}log.info(线程 Thread.currentThread().getName()执行结束);}},A);// 创建线程BThread threadB new Thread(new Runnable() {Overridepublic void run() {log.info(线程 Thread.currentThread().getName()开始执行);// 获取共享变量锁synchronized (obj){// 线程B 唤醒或者中断 调用obj的唤醒操作或者使A线程中断的操作都可以将正在阻塞的A线程唤醒log.info(线程 Thread.currentThread().getName()唤醒);obj.notify(); // 唤醒操作// threadA.interrupted(); // 中断操作}log.info(线程 Thread.currentThread().getName()执行结束);}},B);// 启动线程AthreadA.start();try {// 等待200ms,让线程B获取资源在这200ms期间A就被阻塞了释放了obj对象锁Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}// 启动线程BthreadB.start();}
}执行结果
线程A开始执行
线程B开始执行
线程B执行结束
线程A执行结束
可以看到主程序线程A启动之后休眠了200ms让出cup执行权线程B开始执行后调用notify()方法对阻塞线程A进行唤醒。
故当一个线程调用一个共享变量的wait()方法时该调用线程会被阻塞挂起直到发生下面几件事情之一才返回1其他线程调用了该共享对象的notify()或者notifyAll()方法2其他线程调用了该线程的interrupt()方法该线程抛出InterruptedException异常返回。
1.3 使用技巧
wait()方法一般配合while使用 被唤醒后会重新竞争锁之后从上次wait位置重新运行while 多次判断防止在wait这段时间内对象被修改
二、obj.notify(All)()
notify()和notifyAll()方法也是Object里面的方法Object是所有对象的父类即所有对象都可以调用notify()和notifyAll()方法。但是线程中共享变量在调用这两个方法前该线程需要获取到这个共享变量的锁才可以否则会抛出异常。
1.1 notify() 方法
一个线程调用共享对象的 notify() 方法后会唤醒一个在该共享变量上调用 wait(...) 系列方法后阻塞的线程。
通知线程调用notify/notifyAll方法时所在的线程叫做通知线程。
1.1.1 调用notify()或notifyAll()不会释放线程的锁
当线程调用notify()或notifyAll()方法时它不会释放掉线程持有的锁。
在Java中每个对象都有一个相关联的锁也称为监视器锁。当一个线程需要访问被该锁保护的对象时它必须先获得该锁的所有权。所以只有获得锁的线程才能调用wait()、notify()和notifyAll()方法。
当线程调用notify()或notifyAll()方法时它仅仅是唤醒等待在该对象上的一个或多个线程以便它们可以继续执行。它不会释放线程持有的锁。因此其他线程仍然无法访问被该锁保护的对象直到调用notify()或notifyAll()方法的线程释放锁资源。
在多线程编程中必须小心地管理锁以避免死锁和竞争条件等问题。通常为了确保线程安全和避免死锁必须确保在访问共享资源时只有一个线程持有锁。当然这也需要合理地使用wait()、notify()和notifyAll()方法来协调线程的执行顺序。
值得注意的是
一个共享变量上可能会有多个线程在等待notify()具体唤醒哪个等待的线程是随机的被唤醒的线程不能马上从wait()方法返回并继续执行它必须在获取了共享对象的监视器锁后才可以返回等到唤醒它的线程释放了共享变量上的监视器锁后被唤醒的线程也不一定会获取到共享对象的监视器锁这是因为该线程还需要和其他线程一起竞争该锁只有该线程竞争到了共享变量的监视器锁后才可以继续执行
1.2 notifyAll() 方法
notifyAll() 方法则会唤醒所有在该共享变量上由于调用wait系列方法而被挂起的线程。
1.3 使用技巧
尽量让notify / notifyAll()靠近临界区结束的地方。免得等待线程因为没有获得对象的锁而又进入等待状态。
三、使用实例
比较经典的就是生产者和消费者的例子。
在生产者消费者模型中推荐使用notifyAll因为notify唤醒的线程不确定是生产者或消费者。
public class NotifyWaitDemo {// 共享变量队列的最大容量public static final int MAX_SIZE 1024;// 共享变量public static Queue queue new Queue();public static void main(String[] args) {// 生产者Thread producer new Thread(() - {// 获取共享变量的锁才能调用wait()方法synchronized (queue) {// 一般wait()都配合着while使用因为线程唤醒后需要不断地轮循来尝试获取锁while (true) {// 当队列满了之后就挂起当前线程生产者线程// 并且释放通过queue的监视器锁让消费者对象获取到锁执行消费逻辑if (queue.size() MAX_SIZE) {try {// 阻塞生产者线程并且使当前线程释放掉共享变量的锁queue.wait();} catch (InterruptedException e) {e.printStackTrace();}}// 空闲则生成元素并且通知消费线程queue.add();// 唤醒消费者来消费建议用notifyAll()因为notify()无法确定会唤醒哪一个线程queue.notifyAll();}}});// 消费者Thread consumer new Thread(() - {// 需要先获取锁synchronized (queue) {while (true) {// 当队列已经空了之后就挂起当前线程消费者线程// 并且释放通过queue的监视器锁让生产者对象获取到锁执行生产逻辑if (queue.size() 0) {try {// 阻塞消费者线程并释放共享对象的锁queue.wait();} catch (InterruptedException e) {e.printStackTrace();}}// 空闲则消费元素并且通知生产线程queue.take();queue.notifyAll();}}});// 先执行生产者线程producer.start();try {// 将当前线程睡眠1000ms让生产者先将队列生产满然后wait阻塞起来并且释放持有的锁。为了后续能执行消费者线程Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 执行消费者线程consumer.start();}// 共享变量static class Queue {private int size 0;public int size() {return this.size;}// 生产操作public void add() {// TODOsize;System.out.println(执行add 操作current size: size);}// 消费操作public void take() {// TODOsize--;System.out.println(执行take 操作current size: size);}}
}四、wait()/notify()/notifyAll() 为什么定义在 Object 类中
由于Thread类继承了Object类所以Thread也可以调用者三个方法等待和唤醒必须是同一个锁。而锁可以是任意对象所以可以被任意对象调用的方法是定义在Object类中。 相关文章【并发基础】一篇文章带你彻底搞懂睡眠、阻塞、挂起、终止之间的区别 【并发基础】Java中线程的创建和运行以及相关源码分析 【并发基础】线程进程协程的详细解释 【并发基础】操作系统中线程/进程的生命周期与状态流转以及Java线程的状态流转详解