【AQS】LockSupport工具、Condotion接口
LockSupport:
有一组静态方法,给AQS提供了最基本的阻塞和唤醒功能
方法描述:
park有停车意思,假设当前线程为车辆,阻塞就是停车。
-
park() 阻塞当前线程,如果调用upoark()或者当前线程被中断才能从park()返回
-
parkNanos(long) 超时返回,参数是纳秒,在park基础上增加了时限
-
parkUntil(long deadline) 参数是(从1970年开始到当前时间的毫秒数)
public final boolean awaitUntil(Date deadline) long abstime = deadline.getTime();//返回从1970开始到deadline日期的毫秒数 LockSupport.parkUntil(this, abstime);
- unpark(Thread) 唤醒线程
在java6中,新增了park(Object)、parkNanos(Object,long)、parkUntil(Object,long),其中Object对象用来标识当前线程在等待的对象,该对象主要用来问题排查和系统监控
Condition
多线程基础时有学过,每个Java对象都有一组监视器方法,定义在Object超类里面。主要包括,wait(),wait(long timeout),notify(),notifyAll(),配合synchronized可以时现等待/通知模式。
Object的等待/通知模式
package com.w.juc;
public class WaitAndNotifyDemo {
volatile static int products =0;
private static Object monitor =new Object();
public static void producter() throws InterruptedException {
synchronized(monitor) {
while(true){
if (products == 0){
products++;
Thread.sleep(1000);
System.out.println(Thread.currentThread()+"生产了一个产品,通知消费者消费,现有产品"+products);
monitor.notifyAll();
}else{
System.out.println(Thread.currentThread()+"产品还未消费,等待消费");
monitor.wait();
}
}
}
}
public static void consumer() throws InterruptedException {
synchronized (monitor) {
while(true){
if (products>0){
Thread.sleep(1000);
products--;
System.out.println(Thread.currentThread()+"消费者消费一个产品,通知生产者再次生产,现有产品"+products);
monitor.notifyAll();
}else {
System.out.println(Thread.currentThread()+"还没有产品,等待生成者生产");
monitor.wait();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(()-> {
try {
producter();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"【生产者】").start();
new Thread(()-> {
try {
consumer();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"【消费者】").start();
}
}
这个是我自己写的,按照范例
-
等待方
- 获取对象的锁
- 如果条件不满足就等待,被通知后仍要检查条件
- 条件满足执行
-
通知方
- 获取对象的锁
- 改变条件
- 通知等待的锁
我写的生产者和消费者同时都是等待也是通知方。
生产者先生产一个产品,去通知消费者消费,然后马上再次进入while,此时已经有了产品就会去等待消费者消费
消费者消费完一个产品,马上进入while,此时已经无产品,通知消费者就唤醒了等待中的生产者去生产,然后自己被阻塞等待产品
Condition等待/通知模式
Condition也提供了类似Object的监视器方法,与Lock配合可实现等待/通知模式。
实现示例
通过实现一个有界队列来使用Condition。有界队列:当队列为空时,将当前线程阻塞到空等待队列。当队列满了,将当前线程阻塞到满等待队列
package com.w.juc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 实现有界队列
* 一个固定大小数组
*/
public class ConditionDemo<T> {
private ReentrantLock reentrantLock = new ReentrantLock();
private Condition notFull = reentrantLock.newCondition();
private Condition notEmpty = reentrantLock.newCondition();
private int addIndex,removeIndex,count;
private Object[] items;
public ConditionDemo(int size){
items = new Object[size];
}
public void add(T t){
reentrantLock.lock();
try {
while (count == items.length){
System.out.println(Thread.currentThread()+"想放但满了,不能放了,进入满等待队列");
notFull.await();
}
items[addIndex++] = t;
if (addIndex == items.length){
addIndex = 0;
}
count++;
System.out.println(Thread.currentThread()+"增加成功,当前数组元素个数"+count+"唤醒想删元素的线程");
notEmpty.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
public void remove(){
reentrantLock.lock();
try {
while (count == 0){
System.out.println(Thread.currentThread()+"想删但是空,不能删了,进入空等待队列");
notEmpty.await();
}
items[removeIndex++] = null;
if (removeIndex == items.length){
removeIndex = 0;
}
count--;
System.out.println(Thread.currentThread()+"删除成功,当前数组元素个数"+count+"唤醒想增加元素的线程");
notFull.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
public static void main(String[] args) {
ConditionDemo conditionDemo = new ConditionDemo(2);
new Thread(()->conditionDemo.add("123"),"【Thread1 add】").start();
new Thread(()->conditionDemo.add("123"),"【Thread2 add】").start();
new Thread(()->conditionDemo.add("123"),"【Thread3 add】").start();
new Thread(()->conditionDemo.remove(),"【Thread4 remove】").start();
new Thread(()->conditionDemo.remove(),"【Thread5 remove】").start();
new Thread(()->conditionDemo.remove(),"【Thread6 remove】").start();
new Thread(()->conditionDemo.remove(),"【Thread7 remove】").start();
new Thread(()->conditionDemo.add("123"),"【Thread8 add】").start();
}
}
使用此有界队列:
初始化一个大于为2的有界队列,先通过三个线程增加三个元素,前两个线程添加正常,第三个线程被阻塞
删除一个元素后,第三个添加线程被唤醒,添加元素。
当前元素个数为2,之后连续三个删线程,第三个删线程被阻塞。
当第7个添加线程添加元素后唤醒等待的第7个删线程,删除元素成功,当前元素为0
实现分析
ConditionObject是同步器AQS的内部实现类,每个Condition都包含着一个FIFO单向队列(等待队列)
ConditionObject的主要实现:等待队列,等待和通知。
Condition(ConditionObject) 由lock 通过newCondition创建,使用时必须获取到锁
-
等待队列(conditon queue 也叫条件队列)
等待队列中存放的是,当前线程获取到锁之后,调用了condition.await()方法。会把当前线程构造成一个节点加入到等待队列中,而这个节点则是复用了同步队列中的节点,也就是Node。Condition使用lastWaiter和firstWaiter来记录首尾节点,在Node中复用了nextWaiter用作指向下一下节点的索引
新增节点只需要将新节点添加到队列尾部,并让lastWaiter指向它就行,不需要使用CAS更新。因为此时当前调用await()方法的线程必然获取到了锁
在Object的监视器模式上,有一个同步队列和一个等待队列。而Condition则是有一个同步队列和多个等待队列
2. 等待
当前线程获取锁,然后调用等待awaite(),会将当前线程构建成一个节点,加入到等待队列,线程状态变为等待状态同时释放锁
从队列角度看awaite方法。当调用awaite方法,相当于将首节点移出然后构建一个新的节点放入等待队列队尾。
调用awaite方法,会先构建一个节点加入等待队列,释放同步状态(锁),唤醒同步队列中的后继节点,然后当前线程进入等待状态
如图,当前线程加入到等待队列。同步器的首节点并不会直接移动到等待队列,而是新构建一个节点
3.通知
调用Condition的Sigal方法,会将等待队列中时间最长的节点唤醒,加入到同步队列队尾,通过同步器的acquireQueued方法加入到获取同步状态的竞争
成功获取到同步状态,被唤醒的线程从awaite()方法中返回,表示当前线程成功获取到了锁
如图,是唤醒之后节点从等待队列到了同步队列过程
Condition等待通知本质
当一个线程持有锁,调用awaite()方法:
1. 以当前线程为信息构建一个新Node节点加入等待队列队尾
2. 释放锁,真实最后调用的是Lock本身重写的tryRelease,会唤醒后继节点
3. while自旋,直到被移到了同步队列,就是被signal()
4. 阻塞,调用的是LockSupport的park(Object)方法。
当一个线程持有锁,调用signal()方法:
1. 首先判断当前线程获取到了锁没
2. 将当前线程的状态改为0,初始状态
3. 将当前线程唤醒并放到同步队列
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
//如果当前节点状态为CONDITION,则将状态改为0准备加入同步队列;如果当前状态不为CONDITION,说明该节点等待已被中断,则该方法返回false,doSignal()方法会继续尝试唤醒当前节点的后继节点
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
Node p = enq(node);//将节点加入同步队列,返回的p是节点在同步队列中的先驱节点
int ws = p.waitStatus;
//如果先驱节点的状态为CANCELLED(>0) 或设置先驱节点的状态为SIGNAL失败,那么就立即唤醒当前节点对应的线程
//ws>0代表前驱节点状态已经为CANCELLED(1),可以立即唤醒线程尝试获取同步状态
//cas设置成功,则并不需要现在唤醒,因为等到前驱节点释放后还会再次唤醒线程
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
- 唤醒/或者不唤醒的放到同步队列