关于并发编程:Java-并发编程如何防止在线程阻塞与唤醒时死锁

40次阅读

共计 2330 个字符,预计需要花费 6 分钟才能阅读完成。

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 并发编程:过程、线程、并行与并发

正文完
 0