Java并发编程:多线程如何实现阻塞与唤醒 说到suspend与resume组合有死锁偏向,一不小心将导致很多问题,甚至导致整个零碎解体。接着看另外一种解决方案,咱们能够应用以对象为指标的阻塞,即利用Object类的wait()和notify()办法实现线程阻塞。当线程达到监控对象时,通过wait办法会使线程进入到期待队列中。而当其它线程调用notify时则能够使线程从新回到执行队列中,得以继续执行
思维不同
针对对象的阻塞编程思维须要咱们略微转变下思维,它与面向线程阻塞思维有较大差别。如后面的suspend与resume只需在线程内间接调用就能实现挂起复原操作,这个很好了解。而如果改用wait与notify模式则是通过一个object作为信号,能够将其看成是一堵门。object的wait()办法是锁门的动作,notify()是开门的动作。某一线程一旦关上门后其余线程都将阻塞,直到别的线程打开门。
如图所示,一个对象object调用wait()办法则像是堵了一扇门。线程一、线程二都将阻塞,而后线程三调用object的notify()办法打开门,精确地说是调用了notifyAll()办法,notify()仅仅能让线程一或线程二其中一条线程通过)。最终线程一、线程二得以通过。
死锁问题解决了吗?
应用wait与notify能在肯定水平上防止死锁问题,但并不能完全避免,它要求咱们必须在编程过程中防止死锁。在应用过程中须要留神的几点是:
- 首先,wait与notify办法是针对对象的,调用任意对象的wait()办法都将导致线程阻塞,阻塞的同时也将开释该对象的锁。相应地,调用任意对象的notify()办法则将随机解除该对象阻塞的线程,但它须要从新获取改对象的锁,直到获取胜利能力往下执行。
- 其次,wait与notify办法必须在synchronized块或办法中被调用,并且要保障同步块或办法的锁对象与调用wait与notify办法的对象是同一个。如此一来在调用wait之前以后线程就曾经胜利获取某对象的锁,执行wait阻塞后以后线程就将之前获取的对象锁开释。当然如果你不依照下面规定束缚编写,程序一样能通过编译,但运行时将抛出IllegalMonitorStateException异样,必须在编写时保障用法正确。
- 最初,notify是随机唤醒一条阻塞中的线程并让之获取对象锁,进而往下执行,而notifyAll则是唤醒阻塞中的所有线程,让他们去竞争该对象锁,获取到锁的那条线程能力往下执行。
改良例子
咱们通过wait与notify革新后面的例子,代码如下。革新的思维就是在MyThread中增加一个标识变量,一旦变量扭转就相应地调用wait和notify阻塞唤醒线程。因为在执行wait后将开释synchronized(this)锁住的对象锁,此时System.out.println("running….");早已执行结束,System类out对象不存在死锁问题。
Park与UnPark
wait与notify组合的形式看起来是个不错的解决形式,但其面向的主体是对象object,阻塞的是以后线程,而唤醒的是随机的某个线程或所有线程,偏重于线程之间的通信交互。如果换个角度,面向的主体是线程的话,我就能轻而易举地对指定的线程进行阻塞唤醒,这个时候就须要LockSupport,它提供的park与unpark办法别离用于阻塞和唤醒.而且它提供防止死锁和竞态条件,很好地代替suspend和resume组合。
用park与unpark革新上述例子,代码如下。把主体换成线程进行的阻塞看起来貌似比拟悦目,而且因为park与unpark办法管制的颗粒度更加细小,能精确决定线程在某个点进行,进而防止死锁的产生。例如此例中在执行System.out.println火线程就被阻塞了,于是不存在因竞争System类out对象而产生死锁,即使在执行System.out.println后线程才阻塞也不存在死锁问题,因为锁已开释。
LockSupport 劣势
LockSupport类为线程阻塞唤醒提供了根底,同时,在竞争条件问题上具备wait和notify无可比拟的劣势。应用wait和notify组合时,某一线程在被另一线程notify之前必须要保障此线程曾经执行到wait期待点,错过notify则可能永远都在期待,另外notify也不能保障唤醒指定的某线程。反观LockSupport,因为park与unpark引入了许可机制,许可逻辑为:
- park将许可在等于0的时候阻塞,等于1的时候返回并将许可减为0。
- unpark尝试唤醒线程,许可加1。
依据这两个逻辑,对于同一条线程,park与unpark先后操作的程序仿佛并不影响程序正确地执行。如果先执行unpark操作,许可则为1,之后再执行park操作,此时因为许可等于1间接返回往下执行,并不执行阻塞操作。 最初,LockSupport的park与unpark组合真正解耦了线程之间的同步,不再须要另外的对象变量存储状态,并且也不须要思考同步锁,wait与notify要保障必须有锁能力执行,而且执行notify操作开释锁后还要将以后线程扔进该对象锁的期待队列,LockSupport则齐全不必思考对象、锁、期待队列等问题。
Java 并发编程
- Java并发编程:如何避免在线程阻塞与唤醒时死锁
- Java并发编程:多线程如何实现阻塞与唤醒
- Java并发编程:工作执行器Executor接口
- Java并发编程:并发中死锁的造成条件及解决
- Java并发编程:Java 序列化的工作机制
- Java并发编程:过程、线程、并行与并发