JAVA并发编程——线程等待唤醒机制与LockSupport

1.线程等待唤醒机制概述
2.Object类中的wait和notify方法实现线程等待和唤醒
3.Condition接口中的await后signal方法实现线程的等待和唤醒
4.Object和Condition使用的限制条件
5.LockSupport类简介
6.LockSupport类中的park等待和unpark唤醒
7.总结
1.线程等待唤醒机制概述
通过前面两篇博客:
JAVA并发编程——Synchronized与Lock的区别以及Lock的使用
JAVA并发编程——生产者与消费者模式(传统版&阻塞队列版)
我们初步了解了线程的等待和唤醒机制,并写了几个demo来应征了一个线程来唤醒另外一个线程,但是这两种写法并不是非常完美,具体哪里不完美,以及修改方案,都在一下的博客中进行了解释。
2.Object类中的wait和notify方法实现线程等待和唤醒

public class LockDemo { public static void main(String[] args)//main方法,主线程一切程序入口 { Object objectLock = new Object(); //同一把锁,类似资源类new Thread(() -> { synchronized (objectLock) { try { objectLock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"\t"+"被唤醒了"); },"t1").start(); //暂停几秒钟 Thread.sleep(3000); new Thread(() -> { synchronized (objectLock) { objectLock.notify(); } },"t2").start(); } }

缺陷:
上面的代码虽然能够让一个线程唤醒另外一个线程,但是有着这样的一个缺陷:
1)将notify放在wait方法前面,那么线程t1就无法唤醒
2)wait和notify方法必须要在同步块或者方法里面,且成对出现使用
【JAVA并发编程——线程等待唤醒机制与LockSupport】 3.Condition接口中的await后signal方法实现线程的等待和唤醒
public class LockSupportDemo2 { public static void main(String[] args) { Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); new Thread(() -> { lock.lock(); try { System.out.println(Thread.currentThread().getName()+"\t"+"start"); condition.await(); System.out.println(Thread.currentThread().getName()+"\t"+"被唤醒"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } },"t1").start(); //暂停几秒钟线程 try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) {e.printStackTrace(); }new Thread(() -> { lock.lock(); try { condition.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } System.out.println(Thread.currentThread().getName()+"\t"+"通知了"); },"t2").start(); } }

缺陷:
1) await()方法一定要在signal()方法之前
2) Condtion中的线程等待和唤醒方法之前,需要先获取锁
4.Object和Condition使用的限制条件
从上面可以看出
1) 线程先要获得并持有锁,必须在锁块(synchronized或lock)中
2) 必须要先等待后唤醒,线程才能够被唤醒
5.LockSupport类简介
我们先看看api怎么介绍LockSupport类的:
JAVA并发编程——线程等待唤醒机制与LockSupport
文章图片

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语.
这里我们可能看不懂,我们继续看下面的源码注释:
JAVA并发编程——线程等待唤醒机制与LockSupport
文章图片

基本意思就是说:
LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能, 每个线程都有一个许可(permit)
如果调用park()方法,而且许可(permit为1) 那么线程就会继续执行,否则线程就会立刻阻塞。
permit只有两个值1和零,默认是零。可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1。
6.LockSupport类中的park等待和unpark唤醒
我们先来看一下文档里的park()和unpark()方法:
park() /park(Object blocker) :
阻塞当前线程/阻塞传入的具体线程
JAVA并发编程——线程等待唤醒机制与LockSupport
文章图片

unpark(Thread thread)
唤醒处于阻塞状态的指定线程
JAVA并发编程——线程等待唤醒机制与LockSupport
文章图片

public class LockSupportDemo { public static void main(String[] args) { //正常使用+不需要锁块 Thread t1 = new Thread(() -> { System.out.println(Thread.currentThread().getName()+" "+"1111111111111"); LockSupport.park(); System.out.println(Thread.currentThread().getName()+" "+"2222222222222------end被唤醒"); },"t1"); t1.start(); //暂停几秒钟线程 try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }LockSupport.unpark(t1); System.out.println(Thread.currentThread().getName()+"-----LockSupport.unparrk() invoked over"); } }

优点:
1)错误的先唤醒后等待,LockSupport照样支持
2)无需无锁块要求
缺点:
1)因为标志位1,所以只能唤醒一次线程。连续park() unpark()没有效果。
** 7.总结
今天我们主要学习了以前的线程等待唤醒机制的缺点与改进方法--LockSupport,LockSupport其实是为了阅读以后的aqs源码而进行的一次前置知识,它很好地改进了前面几种唤醒机制的缺陷。

    推荐阅读