Java多线程(三)

104 阅读8分钟

三、共享模式之管程

9. 同步模式之保护性暂停

(1)定义

图片1.png

(2)不带超时的暂停 (代码)

(3)带超时的暂停(代码)

(4)Join 的源码 (底层和上面一样)

10. 异步模式之生产者消费者

(1)定义

图片2.png

(2)基本 (代码)

生产者,消费者的代码

Message 类,拿和取两个方法

多个线程,message对象,一个拿,一个取

11. park/unpark

(1)基本使用

它们是 LockSupport 类中的方法

(2)特点,与wait/notify的区别

wait,notify 和 notifyAll 必须配合Object Monitor一起使用,而park,unpark不必

park ,unpark 是以线程为单位来阻塞和唤醒线程,而 notify 只能随机唤醒一个等待线程,notifyAll 是唤醒所有等待线程,就不那么精确

park & unpark 可以先 unpark(下面有介绍),而 wait & notify 不能先 notify

park不会释放锁,而wait会释放锁

(3)park 的底层原理

每个线程都有一个自己的Park对象,并且该对象_counter, _cond,__mutex组成

情况一:先调用park再调用unpark时*

<1>先调用park

线程运行时,会将Park对象中的_counter的值设为0;

调用park时,会先查看counter的值是否为0,如果为0,则将线程放入阻塞队列cond中

放入阻塞队列中后,会再次将counter设置为0

<2>然后调用unpark

调用unpark方法后,会将counter的值设置为1

去唤醒阻塞队列cond中的线程

线程继续运行并将counter的值设为0

图片3.png

情况二:先调用unpark,再调用park*

<1>调用unpark

会将counter设置为1(运行时0)

<2>调用park方法

查看counter是否为0

因为unpark已经把counter设置为1,所以此时将counter设置为0,但不放入阻塞队列cond中

图片4.png

图片5.png

12. 线程中的状态转换

图片6.png

(1) 情况一:NEW –> RUNNABLE 。初始状态。*

当调用了t.start()方法时,由 NEW –> RUNNABLE  

(2) 情况二: RUNNABLE <–> WAITING*

当调用了t 线程用 synchronized(obj) 获取了对象锁后

调用 obj.wait() 方法时,t 线程从 RUNNABLE –> WAITING

调用 obj.notify() , obj.notifyAll() , t.interrupt() 时

竞争锁成功,t 线程从 WAITING –> RUNNABLE

竞争锁失败,t 线程从 WAITING –> BLOCKED

(3)情况三:RUNNABLE <–> WAITING

当前线程调用 t.join() 方法时,当前线程从 RUNNABLE –> WAITING

注意是当前线程在t 线程对象的监视器上等待

t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 WAITING –> RUNNABLE

(4)情况四: RUNNABLE <–> WAITING

当前线程调用 LockSupport.park() 方法会让当前线程从 RUNNABLE –> WAITING

调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,会让目标线程从 WAITING –> RUNNABLE

(5)情况五: RUNNABLE <–> TIMED_WAITING

t 线程用 synchronized(obj) 获取了对象锁后

调用 obj.wait(long n) 方法时,t 线程从 RUNNABLE –> TIMED_WAITING

t 线程等待时间超过了 n 毫秒,或调用 obj.notify() , obj.notifyAll() , t.interrupt() 时

竞争锁成功,t 线程从 TIMED_WAITING –> RUNNABLE

竞争锁失败,t 线程从 TIMED_WAITING –> BLOCKED

(6)情况六:RUNNABLE <–> TIMED_WAITING

当前线程调用 t.join(long n) 方法时,当前线程从 RUNNABLE –> TIMED_WAITING

注意是当前线程在t 线程对象的监视器上等待

当前线程等待时间超过了 n 毫秒,或t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 TIMED_WAITING –> RUNNABLE

(7)情况七:RUNNABLE <–> TIMED_WAITING

当前线程调用 Thread.sleep(long n) ,当前线程从 RUNNABLE –> TIMED_WAITING

当前线程等待时间超过了 n 毫秒,当前线程从 TIMED_WAITING –> RUNNABLE

(8)情况八:RUNNABLE <–> TIMED_WAITING

当前线程调用 LockSupport.parkNanos(long nanos) 或 LockSupport.parkUntil(long millis) 时,当前线 程从 RUNNABLE –> TIMED_WAITING

调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,或是等待超时,会让目标线程从 TIMED_WAITING–> RUNNABLE

(9)情况九:RUNNABLE <–> BLOCKED。简单说线程获取锁失败(资源获取失败)便会进入BLOCKED状态。

t 线程用 synchronized(obj) 获取了对象锁时如果竞争失败,从 RUNNABLE –> BLOCKED

持 obj 锁线程的同步代码块执行完毕,会唤醒该对象上所有 BLOCKED 的线程重新竞争,如果其中 t 线程竞争 成功,从 BLOCKED –> RUNNABLE ,其它失败的线程仍然 BLOCKED

(10)情况十: RUNNABLE <–> TERMINATED。结束状态。

当前线程所有代码运行完毕,进入 TERMINATED

总结: 除了初始状态和结束状态。剩下的是从Runnable--> WAITING(TIMED_WAITING)。实现这种状态转换,包括一下几种方法。

  • Thread方法。
  • t类方法。当前线程调用t.join(),  当前线程进入阻塞状态。
  • LockSupport方法。
  • 对象方法,Object.wait()和notify()。o.wait()表示该资源缺少(释放锁),会使得线程进入waiting(timed waiting) 状态。o.notify()与之相反,表示该资源已经有了--> 可以runnable, 如何资源竞争失败,则为blocked。

13. 多把锁

将锁的粒度细分

class BigRoom {

    //额外创建对象来作为锁

private final Object studyRoom = new Object();

private final Object bedRoom = new Object();

}

14. 活跃性

(1)定义

因为某种原因,使得代码一直无法执行完毕,这样的现象叫做活跃性

(2)死锁

<1>有这样的情况

一个线程需要同时获取多把锁,这时就容易发生死锁

发生死锁的必要条件

<2>互斥条件

在一段时间内,一种资源只能被一个进程所使用

请求和保持条件

进程已经拥有了至少一种资源,同时又去申请其他资源。因为其他资源被别的进程所使用,该进程进入阻塞状态,并且不释放自己已有的资源

不可抢占条件

进程对已获得的资源在未使用完成前不能被强占,只能在进程使用完后自己释放

循环等待条件

发生死锁时,必然存在一个进程——资源的循环链。

<3>哲学家就餐问题

锁对象环形,导致死锁

<4>避免死锁的方法

在线程使用锁对象时,顺序加锁即可避免死锁

(3)活锁

定义: 活锁出现在两个线程互相改变对方的结束条件,后谁也无法结束。

避免活锁的方法: 在线程执行时,中途给予不同的间隔时间即可

死锁与活锁的区别:  (1)死锁是因为线程互相持有对象想要的锁,并且都不释放,最后到时线程阻塞,停止运行的现象。(2)活锁是因为线程间修改了对方的结束条件,而导致代码一直在运行,却一直运行不完的现象。

(4)饥饿

某些线程因为优先级太低,导致一直无法获得资源的现象。

在使用顺序加锁时,可能会出现饥饿现象

15. ReentrantLock(新的锁)关键字和原理(原理部分在后面介绍)

(1)synchronized相比

<1>相同的地方:

l 可重入。与 synchronized 一样,都支持

<2>不同的地方:

  • 可中断
  • 可以设置超时时间
  • 可以设置为公平锁 (先到先得)
  • 支持多个条件变量( 具有多个waitset)

(2)基本语法

// 获取锁

reentrantLock.lock();

try {

 // 临界区

} finally {

 // 释放锁

 reentrantLock.unlock();

}

(3)可重入

可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁。如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住。

(4)可打断

模式一:不可打断。在此模式下,即使它被打断,仍会驻留在 AQS 队列中,一直要等到获得锁后方能得知自己被打断了

模式二:可打断。

ReentrantLock的lockInterruptibly()方法可以在等锁的过程中被interrupt打断,结束等待。另外,synchronized不可打断指的是synchronized等待不可中断,或者说只有获取到锁之后才能中断。

什么情况下可被中断?

根据Java文档,判断方法很简单:只要调用的方法抛出InterruptedException异常,那么它就可以被中断;不抛出InterruptedException的方法是不可中断的。

(5)锁超时

使用lock.tryLock方法会返回获取锁是否成功。如果成功则返回true,反之则返回false。并且tryLock方法可以指定等待时间,参数为:tryLock(long timeout, TimeUnit unit), 其中timeout为最长等待时间,TimeUnit为时间单位。简而言之就是:获取失败了、获取超时了或者被打断了,不再阻塞,直接停止运行。

(6)公平锁

在线程获取锁失败,进入阻塞队列时,先进入的会在锁被释放后先获得锁。这样的获取方式就是公平的。默认是不公平锁,需要在创建时指定为公平锁

(7)条件变量

synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入waitSet 等待。ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比synchronized 是那些不满足条件的线程都在一间休息室等消息。而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒,而不是同意唤醒。

使用要点:

await 前需要获得锁

await 执行后,会释放锁,进入 conditionObject 等待

await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁

竞争 lock 锁成功后,从 await 后继续执

16. 同步模式之顺序控制 (代码)

(1)固定顺序

Wait/ notify ( 布尔值控制 true<-->false)

Park/ unpark

(2)交替顺序

Wait/ notify (a,b,c 控制)

Park/ unpark

Await/signal ( Reentrantlock)