-
2021-12-10 21:10:16
前言
当多个线程处理同一资源时,如果这些线程完成的任务不同,我们可能会需要让这些线程以一定规律来处理这一资源。对于这个需求,Java提供了等待唤醒机制来让多个线程协调运行。
wait(),notify()和notifyAll()
等待唤醒机制顾名思义就是使线程进入等待状态或唤醒等待状态下的线程。使用
wait()
方法可以让所属锁的当前线程进入wait set中,变为等待状态,并且释放所属锁,使得锁对象对应的其他线程获得此锁来执行相应的任务;而notify()
方法可以唤醒锁对象对应的等待状态下的某个线程,让它重新回到调度队列中(ready queue),与其他线程共同竞争锁,获取锁后会在当初wait()
方法后恢复执行。这两个方法需要在同步下才能生效,而且需要在当前获取的锁对象上调用,来表明这两个方法操作的是哪个锁对象对应的线程。
notifyAll()
方法可以唤醒所有等待状态下的线程。参考资料
更多相关内容 -
java线程阻塞唤醒的四种方式
2021-02-13 02:20:49java线程阻塞唤醒的四种方式 java在多线程情况下,经常会使用到线程的阻塞与唤醒,这里就为大家简单介绍一下以下几种阻塞/唤醒方式与区别,不做详细的介绍与代码分析suspend与resumeJava废弃 suspend() 去挂起线程的...原标题:java线程阻塞唤醒的四种方式
java在多线程情况下,经常会使用到线程的阻塞与唤醒,这里就为大家简单介绍一下以下几种阻塞/唤醒方式与区别,不做详细的介绍与代码分析
suspend与resume
Java废弃 suspend() 去挂起线程的原因,是因为 suspend() 在导致线程暂停的同时,并不会去释放任何锁资源。其他线程都无法访问被它占用的锁。直到对应的线程执行 resume() 方法后,被挂起的线程才能继续,从而其它被阻塞在这个锁的线程才可以继续执行。
但是,如果 resume() 操作出现在 suspend() 之前执行,那么线程将一直处于挂起状态,同时一直占用锁,这就产生了死锁。而且,对于被挂起的线程,它的线程状态居然还是 Runnable。
wait与notify
wait与notify必须配合synchronized使用,因为调用之前必须持有锁,wait会立即释放锁,notify则是同步块执行完了才释放
await与singal
Condition类提供,而Condition对象由new ReentLock().newCondition()获得,与wait和notify相同,因为使用Lock锁后无法使用wait方法
park与unpark
LockSupport是一个非常方便实用的线程阻塞工具,它可以在线程任意位置让线程阻塞。和Thread.suspenf()相比,它弥补了由于resume()在前发生,导致线程无法继续执行的情况。和Object.wait()相比,它不需要先获得某个对象的锁,也不会抛出IException异常。可以唤醒指定线程。
总结
wait与await区别:
wait与notify必须配合synchronized使用,因为调用之前必须持有锁,wait会立即释放锁,notify则是同步块执行完了才释放
因为Lock没有使用synchronized机制,故无法使用wait方法区操作多线程,所以使用了Condition的await来操作
Lock实现主要是基于AQS,而AQS实现则是基于LockSupport,所以说LockSupport更底层,所以使用park效率会高一些
责任编辑:
-
Java并发:挂起与唤醒线程LockSupport工具类详解
2021-12-07 15:53:16LockSupport概述 LockSupport工具类定义了一组公共的静态方法,提供了最基本的线程阻塞和唤醒...// 挂起线程 private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupLockSupport概述
LockSupport工具类定义了一组公共的静态方法,提供了最基本的线程阻塞和唤醒功能,是创建锁和其他同步类的基础,你会发现,AQS中阻塞线程和唤醒线程的地方,就是使用LockSupport提供的park和unpark方法,比如下面这段:
// 挂起线程 private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); } // 唤醒线程 private void unparkSuccessor(Node node) { //... if (s != null) LockSupport.unpark(s.thread); }
park与unpark相关方法
LockSupport提供了一组park开头的方法来阻塞当前线程【省略static】:
void park():阻塞当前线程,如果调用unpark(Thread thread)方法或者当前线程被中断,才能从park()方法返回。
void parkNanos(long nanos):阻塞当前线程,最长不超过nanos纳秒,返回条件在park()的基础上增加了超时返回。
void parkUntil(long deadline):阻塞当前线程,直到deadline【从1970年开始到deadline时间的毫秒数】时间。
void unpark(Thread thread):唤醒处于阻塞状态的线程thread。
JDK1.6中,增加了带有blocker参数的几个方法,blocker参数用来标识当前线程在等待的对象,用于问题排查和系统监控。
下面演示park()方法和unpark()方法的使用:
在thread线程中调用park()方法,默认情况下该线程是不持有许可证的,因此将会被阻塞挂起。 unpark(thread)方法将会让thread线程获得许可证,才能从park()方法返回。
public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() ->{ String name = Thread.currentThread().getName(); System.out.println(name + " begin park"); LockSupport.park();// 如果调用park的线程已经获得了关联的许可证,就会立即返回 System.out.println(name + " end park"); },"A"); thread.start(); // 默认情况下,thread不持有许可证,会被阻塞挂起 Thread.sleep(1000); System.out.println(thread.getName() + " begin unpark"); LockSupport.unpark(thread);//让thread获得许可证 }// 结果如下 A begin park A begin unpark A end park
你需要理解,许可证在这里的作用,我们也可以事先给线程一个许可证,接着在park的时候就不会被阻塞了。
public static void main(String[] args) { System.out.println("begin park"); // 使当前线程获得许可证 LockSupport.unpark(Thread.currentThread()); // 再次调用park方法,因为已经有许可证了,不会被阻塞 LockSupport.park(); System.out.println("end park"); }// 结果如下 begin park end park
中断演示
线程被中断的时候,park方法不会抛出异常,因此需要park退出之后,对中断状态进行处理。
public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { String name = Thread.currentThread().getName(); System.out.println(name + " begin park"); // 一直挂起自己,只有被中断,才会推出循环 while (!Thread.currentThread().isInterrupted()) { LockSupport.park(); } System.out.println(name + " end park"); }, "A"); thread.start(); Thread.sleep(1000); System.out.println("主线程准备中断线程" + thread.getName()); // 中断thread thread.interrupt(); }// 结果如下 A begin park 主线程准备中断线程A A end park
blocker的作用
JDK1.6开始,一系列park方法开始支持传入blocker参数,标识当前线程在等待的对象,当线程在没有持有许可证的情况下调用park方法而被阻塞挂起时,这个blocker对象会被记录到该线程内部。
public static void park(Object blocker) { Thread t = Thread.currentThread(); setBlocker(t, blocker); // 设置blocker UNSAFE.park(false, 0L); setBlocker(t, null); // 清除blocker }
Thread类里有个volatile Object parkBlocker变量,用来存放park方法传递的blocker对象,也就是把blocker变量存放到了调用park方法的线程的成员变量中。
接下来我们通过两个例子感受一下:
测试无blocker
public class TestParkWithoutBlocker { public void park(){ LockSupport.park(); } public static void main(String[] args) throws InterruptedException { new TestParkWithoutBlocker().park(); Thread.sleep(3000); } }
使用jps命令,列出当前运行的进程4412 TestPark,接着使用jstack 4412命令查看线程堆栈:
测试带blocker
public class TestBlockerPark { public void park(){ LockSupport.park(this); // 传入blocker = this } public static void main(String[] args) throws InterruptedException { new TestBlockerPark().park(); Thread.sleep(3000); } }
明显的差别就在于,使用带blocker 参数的park方法,能够通过jstack看到具体阻塞对象的信息:
- parking to wait for <0x000000076b77dff0> (a chapter6_1_LockSupport.TestBlockerPark)
诊断工具可以调用getBlocker(Thread)方法来获取blocker对象,JDK推荐我们使用带有blocker参数的park方法,并且设置blocker为this,这样当在打印线程堆栈排查问题的时候就能够知道那个类被阻塞了。
JDK提供的demo
老传统了,摘一段JavaDoc上的使用案例:
/** * 先进先出的锁,只有队列的首元素可以获取锁 */ class FIFOMutex { private final AtomicBoolean locked = new AtomicBoolean(false); private final Queue<Thread> waiters = new ConcurrentLinkedQueue<Thread>(); public void lock() { // 中断标志 boolean wasInterrupted = false; Thread current = Thread.currentThread(); waiters.add(current); // 不是队首线程 或 当前锁已经被其他线程获取,则调用park方法挂起自己 while (waiters.peek() != current || !locked.compareAndSet(false, true)) { LockSupport.park(this); // 如果park方法是因为被中断而返回,则忽略中断,并且重置中断标志 // 接着再次进入循环 if (Thread.interrupted()) // ignore interrupts while waiting wasInterrupted = true; } waiters.remove(); // 如果标记为true,则中断线程 // [虽然我对中断信号不感兴趣,忽略它,但是不代表其他线程对该标志不感兴趣,因此恢复一下.] if (wasInterrupted) // reassert interrupt status on exit current.interrupt(); } public void unlock() { locked.set(false); LockSupport.unpark(waiters.peek()); } }
总结
LockSupport提供了有关线程挂起park和唤醒unpark的静态方法。
JDK1.6之后允许传入blocker阻塞对象,便于问题监控和排查。
如果park的线程被中断,不会抛出异常,需要自行对中断状态进行处理。
原文链接:
https://www.cnblogs.com/summerday152/p/14290036.html如果觉得本文对你有帮助,就点赞关注支持一下吧!
-
java多线程通信之等待唤醒机制
2020-12-22 19:29:31唤醒在此对象监视器上等待的单个线程,如果所有线程都在此对象上等待,则会任意选择唤醒其中一个线程。 public final void wait() 当前线程必须拥有此对象监视器。该线程发布对此监视器的所有权并等待,... -
Java线程阻塞和唤醒的几种方式
2020-12-17 10:04:09使用wait()方法来阻塞线程,使用notify()和notifyAll()方法来唤醒线程。 调用wait()方法后,线程将被阻塞,wait()方法将会释放当前持有的监视器锁(monitor),直到有线程调用notify/notifyAll()方法后方能继续执行。 ...Object类自带的方法
使用wait()方法来阻塞线程,使用notify()和notifyAll()方法来唤醒线程。
调用wait()方法后,线程将被阻塞,wait()方法将会释放当前持有的监视器锁(monitor),直到有线程调用notify/notifyAll()方法后方能继续执行。
notify/notifyAll()方法只是解除了等待线程的阻塞,并不会马上释放监视器锁,而是在相应的被synchronized关键字修饰的同步方法或同步代码块执行结束后才自动释放锁。
默认使用非公平锁,无法修改。
缺点:- 使用几个方法时,必须处于被synchronized关键字修饰的同步方法或同步代码块中,否则程序运行时,会抛出IllegalMonitorStateException异常。
- 线程的唤醒必须在线程阻塞之后,否则,当前线程被阻塞之后,一直没有唤醒,线程将会一直等待下去(对比LockSupport)
public class SynchronizedDemo { // 三个线程交替打印ABC public static void main(String[] args) { Print print = new Print(); new Thread(() -> { while (true) { print.printA(); } }, "A").start(); new Thread(() -> { while (true) { print.printB(); } }, "B").start(); new Thread(() -> { while (true) { print.printC(); } }, "C").start(); } } class Print { Object object = new Object(); int num = 1; public void printA() { synchronized (object) { try { while (num != 1) { object.wait(); } for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "==>A"); } num = 2; object.notifyAll(); } catch (InterruptedException e) { e.printStackTrace(); } } } public void printB() { synchronized (object) { try { while (num != 2) { object.wait(); } for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + "==>B"); } num = 3; object.notifyAll(); } catch (InterruptedException e) { e.printStackTrace(); } } } public void printC() { synchronized (object) { try { while (num != 3) { object.wait(); } for (int i = 0; i < 15; i++) { System.out.println(Thread.currentThread().getName() + "==>C"); } num = 1; object.notifyAll(); } catch (InterruptedException e) { e.printStackTrace(); } } } }
Condition接口
使用await()方法来阻塞线程,signal()/singnalAll()方法来唤醒线程。
需要使用lock对象的newCondition()方法获得Condition条件对象(可有多个)。
可实现公平锁,默认是非公平锁
缺点:- 必须被Lock包裹,否则会在运行时抛出IllegalMonitorStateException异常。
- 线程的唤醒必须在线程阻塞之后
- Lock的实现是基于AQS,效率稍高于synchronized
public class ConditionDemo { // 三个线程交替打印ABC public static void main(String[] args) { Print print = new Print(); new Thread(() -> { while (true) { print.printA(); } }, "A").start(); new Thread(() -> { while (true) { print.printB(); } }, "B").start(); new Thread(() -> { while (true) { print.printC(); } }, "C").start(); } } class Print { private Lock lock = new ReentrantLock(); private Condition condition1 = lock.newCondition(); private Condition condition2 = lock.newCondition(); private Condition condition3 = lock.newCondition(); private int num = 1; public void printA() { lock.lock(); try { while (num != 1) { condition1.await(); } for (int i = 0; i < 5; ++i) { System.out.println(Thread.currentThread().getName() + "==>A"); } num = 2; condition2.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void printB() { lock.lock(); try { while (num != 2) { condition2.await(); } for (int i = 0; i < 10; ++i) { System.out.println(Thread.currentThread().getName() + "==>B"); } num = 3; condition3.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void printC() { lock.lock(); try { while (num != 3) { condition3.await(); } for (int i = 0; i < 15; ++i) { System.out.println(Thread.currentThread().getName() + "==>C"); } num = 1; condition1.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }
LockSupport
使用park()来阻塞线程,用unpark()方法来唤醒线程。
这里有一个许可证的概念,许可不能累积,并且最多只能有一个许可,只有1和0的区别。
特点:- 使用灵活,可以直接使用
- 线程唤醒可在线程阻塞之前,因为调用unpark()方法后,线程已经获得了一个许可证(但也只能有一个许可证),之后阻塞时,可以直接使用这个许可证来通行。
- 效率高
public class LockSupportDemo { // 三个线程交替打印ABC public static void main(String[] args) throws Exception { Print print = new Print(); Thread threadA = new Thread(() -> { while (true) { print.printA(); } }, "A"); Thread threadB = new Thread(() -> { while (true) { print.printB(); } }, "B"); Thread threadC = new Thread(() -> { while (true) { print.printC(); } }, "C"); threadA.start(); threadB.start(); threadC.start(); while (true) { LockSupport.unpark(threadA); LockSupport.unpark(threadB); LockSupport.unpark(threadC); } } } class Print { private int num = 1; public void printA() { while (num != 1) { LockSupport.park(); } for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "==>A"); } num = 2; } public void printB() { while (num != 2) { LockSupport.park(); } for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + "==>B"); } num = 3; } public void printC() { while (num != 3) { LockSupport.park(); } for (int i = 0; i < 15; i++) { System.out.println(Thread.currentThread().getName() + "==>C"); } num = 1; } }
-
Java中如何唤醒“指定的“某个线程
2021-02-28 15:42:16熟悉线程操作的小朋友应该知道,Java中线程的挂起和唤醒一般用synchronized + wait + notify完成。比如:synchronized(o) {o.wait(); //wait状态}在其他线程中o.notify(),就可以唤醒在o上wait的线程。可是如果o上有... -
Java线程等待唤醒机制(加深理解)
2019-08-04 16:28:06今天看源码的时候遇到这样一个场景,某...下面代码是一个简单的线程唤醒机制示例,主要就是在Activity启动的时候初始化并start线程,线程start后会进入等待状态,在onResume方法中执行notify方法唤醒线程。通过这样... -
Java中如何唤醒“指定的“某线程
2021-03-05 20:33:26熟悉线程操作的小伙伴应该知道,Java中线程的挂起和唤醒一般用synchronized + wait + notify完成。比如:synchronized(o) {o.wait(); //wait状态}在其他线程中o.notify(),就可以唤醒在o上wait的线程。可是如果o上有... -
java并发编程基础--线程的挂起、恢复及中断操作
2019-02-18 16:55:30一、线程挂起与恢复 1、在久的JDK版本中,提供了下面两个方法,但目前已经废弃了,Thread源码中都加了@Deprecated注解 thread.suspend() 该方法不会释放线程所占用的资源。如果使用该方法将某个线程挂起,则可能会... -
Java 中如何唤醒阻塞线程?
2021-02-28 11:59:50如果线程遇到了IO阻塞,无能为力,因为IO是操作系统实现的,Java代码并没有办法直接接触到操作系统。以下是详细的唤醒方法:1. sleep() 方法sleep(毫秒),指定以毫秒为单位的时间,使线程在该时间内进入线程阻塞状态... -
Java 多线程使用:线程的挂起与重新唤醒
2013-09-27 20:19:00这两天在研究多线程的使用方法,我们的需求是这样的:程序启动时,先让子线程处于运行状态,运行过程中会人为的更改该线程的运行状态为挂起,等待随机的时长之后,再把该线程重新唤醒,让其继续运行;人为挂起线程和... -
线程挂起和唤醒,LockSupport
2020-06-14 21:04:25LockSupport 可以挂起和唤醒线程 park() 和 unpark() 是两个静态方法分别表示挂起和唤醒 unpark() 可以执行在 park() 之前。标识这个线程已经做过唤醒操作,线程会直接略过阻塞。 package multhread; import java... -
Java中如何唤醒“指定的“线程
2021-02-16 22:36:50熟悉线程操作的小伙伴应该知道,Java中线程的挂起和唤醒一般用synchronized + wait + notify完成。 比如: synchronized(o) { o.wait(); //wait状态 } 在其他线程中o.notify(),就可以唤醒在o上wait的线程。 ... -
Java线程的挂起与唤醒
2013-08-17 14:11:15private Thread mythread; public void start() { if (mythread==null){ mythread=new Thread(); mythread.start(); } else { mythread.resume(); } } public void run() ...try{ -
Java线程的挂起与恢复wait(), notify()方法介绍
2019-03-23 21:58:07所谓线程挂起就是指暂停线程的执行(阻塞状态). 而恢复时就是让暂停的线程得以继续执行.(返回就绪状态) 二, 为何需要挂起和恢复线程. 我们来看1个经典的例子(生产消费): 1个仓库最多容纳6个产品, 制造者现在需要... -
Java线程阻塞与唤醒
2018-05-07 00:35:38Thread.suspend和Thread.resume因为容易导致死锁,很早以前就被标记为@deprecated,不建议使用了。我们把线程使用互斥锁访问的共享资源叫做...如果使用resume方法唤醒线程前需要获取监视器,那么死锁就会发生。当然... -
java并发学习-线程的睡眠与唤醒
2020-05-23 17:14:30java中每一个对象在运行时都会拥有一个对象头,用于存储对象的一些附加信息。普通的对象头部组成(以64为虚拟机为例)如下图。其中Mark Word主要用来存储对象的运行时数据;Klass用于存储对象的类型指针,该指针指向它... -
Java线程挂起
2017-06-07 21:47:00Java提供了2个关于线程挂起和恢复的接口,一个是suspend用于挂起线程,一个是resume接口用于唤醒线程。这2个线程都是被废弃的接口,因为他们和stop一样是不安全的。 suspend挂起线程不会释放线程当前占用的资源,... -
java线程技术6_线程的挂起和唤醒
2013-03-01 21:26:10在线程挂起后,可以通过重新唤醒线程来使之恢复运行。 挂起的原因可能是如下几种情况: (1)通过调用sleep()方法使线程进入休眠状态,线程在指定时间内不会运行。 (2)通过调用join()方法使线程挂起,使... -
Java多线程如何实现阻塞与唤醒(上)
2020-10-16 15:35:39跟着作者的65节课彻底搞懂Java并发原理专栏,一步步彻底搞懂Java并发原理。 作者简介:笔名seaboat,擅长工程算法、人工智能算法、自然语言处理、计算机视觉、架构、...线程的阻塞和唤醒在多线程并发过程中是一. -
Java实现线程唤醒与阻塞的方法
2018-07-06 15:08:56如果线程遇到了IO阻塞,无能为力,因为IO是操作系统实现的,Java代码并没有办法直接接触到操作系统。以下是详细的唤醒方法:1. sleep() 方法:sleep(毫秒),指定以毫秒为单位的时间,使线程在该时间内进入线程... -
多线程之Java线程阻塞与唤醒
2014-12-06 18:58:01线程的阻塞和唤醒在多...在Java发展史上曾经使用suspend()、resume()方法对于线程进行阻塞唤醒,但随之出现很多问题,比较典型的还是死锁问题。如下代码,主要的逻辑代码是主线程启动线程mt一段时间后尝试使用suspend -
java 线程的挂起和唤醒
2014-03-07 15:56:41这三个方法依赖于同一个锁对象,,也就是说只能采用同一个锁的对象的notify唤醒该锁wait造成的挂起。注意在调用notify和wait方法的时候,首先得获取该锁的使用权限。 wait 方法将线程阻塞。如果有十个线程调用了... -
Java消费者生产者|线程等待线程唤醒WaitNotifyDemo
2015-12-13 15:29:03Java写的消费者生产者模式,主要用到线程同步、线程等待和线程唤醒 -
Java的wait(), join(), sleep, lockSupport.park, 线程阻塞,等待,挂起等状态详解
2021-04-09 15:25:50} 以上源码就解释了什么是阻塞,等待,但是没有挂起,为什么没有挂起状态,因为线程挂起这种操作已经过时,不建议使用了,这里不做过多讨论,有人愿意研究的,请自行搜索:线程挂起。不要搜跟阻塞,等待的区别,很... -
park(), wait(), sleep()线程的挂起/唤醒的基本使用与区别
2021-06-23 16:33:48直接上代码,先看一段很简单的创建线程的代码: Thread threadB = new Thread(new Runnable() { @Override ...这个时候理所当然就想着先把当前线程挂起,满足条件时候再唤醒。经过改造,写出如下代码: