Lock&Condition&AQS
Lock是一个接口,以ReentrantLock为例。ReentrantLock实现了Lock接口,ReentrantLock中有一个newCondition方法,这个方法调用了ReentrantLock的内部类Sync的newCondition方法,Sync的newCondition方法返回的是一个ConditionObject对象,这个对象是AbstractQueuedSynchronizer的一个内部类。AbstractQueuedSynchronizer的内部类ConditionObject实现了Condition接口。
Lock&Condition&AQS的使用
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ProducerConsumer {
//lock
private final Lock lock = new ReentrantLock();
//数组未满条件condition
private final Condition notFull = lock.newCondition();
//数组非空条件condition
private final Condition notEmpty = lock.newCondition();
//存储数据的底层数组
private final Object[] items = new Object[100];
//输入数据的索引位置
private int inputIndex;
//输出数据的索引位置
private int outputIndex;
//计数器
private int count;
//日期格式化
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:MM:ss SSS");
public static void main(String[] args) {
int corePoolSize = 10;
int maxPoolSize = 10;
long keepAliveTime = 10;
TimeUnit unit = TimeUnit.SECONDS;
ThreadPoolExecutor executor =null;
BlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<Runnable>(10);
try{
executor = new ThreadPoolExecutor(corePoolSize,maxPoolSize,keepAliveTime,unit,blockingQueue);
executor.prestartAllCoreThreads();
ProducerConsumer producerConsumer = new ProducerConsumer();
for (int i = 0; i < 10; i++) {
executor.submit(()->{
for (int j = 0; j < 10; j++) {
try {
Thread.sleep(5);
producerConsumer.put(String.format("生产者%s于%s生产一条数据,",Thread.currentThread().getName(),printDate()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
executor.submit(()->{
try {
for (int i = 0; i < 100; i++) {
Object outPut = producerConsumer.take();
System.out.printf("消费者获取到数据%s%n",outPut);
}
}catch (InterruptedException e){
e.printStackTrace();
}
});
}finally {
assert executor != null;
executor.shutdown();
}
}
/**
* 生产者方法,往数组里写数据
*
* @param input 输入数据
* @throws InterruptedException 中断异常
*/
public void put(Object input) throws InterruptedException {
lock.lock();
try{
while (count == items.length){
//数组已满,没有空间时,线程挂起等待,直到数组“非满“
notFull.await();
}
items[inputIndex] = input;
if (++inputIndex == items.length){
inputIndex = 0;
}
++count;
//因为添加了一个数据,数据肯定不为空
//此时唤醒等待在notEmpty条件的线程
notEmpty.signal();
}finally {
lock.unlock();
}
}
/**
* 消费者方法,从数组里拿数据
*
* @return 数据
* @throws InterruptedException 中断异常
*/
public Object take() throws InterruptedException {
lock.lock();
try{
while (count == 0){
//数组是空的,没有数据可拿,挂起等待,直到数组非空
notEmpty.await();
}
Object x = items[outputIndex];
if (++outputIndex == items.length){
outputIndex = 0;
}
--count;
//因为拿出了一个数据,此时数组一定是非满的
//此时唤醒在notFull上等待的线程
notFull.signal();
return x;
}finally {
lock.unlock();
}
}
/**
* 打印时间
*/
private static String printDate(){
return "【当前时间:" + simpleDateFormat.format(new Date()) + "】";
}
}
Lock接口源码解析
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long var1, TimeUnit var3) throws InterruptedException;
void unlock();
Condition newCondition();
锁的获取
Lock接口定义了4中获取锁的方式
lock()方法
阻塞式获取,在没有获取到锁时,当前线程会阻塞,不会参与线程调度,直到获取到锁为止,获取过程中不响应中断。
lockInterruptibly()方法
阻塞式获取,并且可以中断,该方法将在以下两种情况之一发生的情况下抛出InterruptedExcecption。在InterruptedException抛出后,当前线程的中断标志位将会被清除。
- 在调用该方法时,线程的中断标志位已经被设为0了
- 在获取锁的过程中,线程被中断了,并且锁的获取实现会响应这个中断
tryLock()方法
非阻塞式获取,无论成功与否,该方法都是立刻返回。相比前面两种阻塞式获取的方式,该方法是有返回值的,获取锁成功了则返回true,获取锁失败了则返回false。
tryLock(long time, TimeUnit until)
- 带超时机制,并且可以中断
- 如果可以获取到锁,则立刻返回true
- 如果获取不到锁,则当前线程将会休眠,不会参与线程调度,直到以下三个条件之一被满足
- 当前线程获取到了锁
- 其他线程中断了当前线程
- 设定的超时时间到了
- 该方法将在以下两种情况之一发生的情况下抛出InterruptedException
- 在调用该方法时,线程的中断标志位已经被设为0了
- 在获取锁的过程中,线程被中断了,并且锁的获取实现会响应这个中断
- 在InterruptedException抛出后,当前线程的中断标识位将会被清除
- 如果超时时间到了,当前线程还没有获取到锁,则会直接返回false(这里并没有抛出超时异常)
其实tryLock(long time, TimeUnit until)更像是阻塞式与非阻塞式的结合体,即在一定条件下(超时时间内,没有中断发生)阻塞,不满足这个条件则立刻返回(非阻塞)。
总结
Lock——锁的获取的方式 | |
lock() | 阻塞式 |
lockInterruptibly() | 阻塞式 可中断 |
tryLock() | 非阻塞式 |
tryLock(long time, TimeUnit until) | 超时机制 可中断 |
锁的释放
相对于锁的获取,锁的释放的方法就简单的多,只有一个
unlock()方法
值得注意的是,只有拥有的锁的线程才能释放锁,并且,必须显式的释放锁,这一点和离开同步代码块就自动被释放的监视器锁是不同的。
Condition接口
Lock接口还定义了一个newCondition方法
Condition newCondition();
该方法将创建一个绑定在当前Lock对象上的Condition对象,这说明Condition对象和Lock对象是对应的,一个Lock对象可以创建多个Condition对象,它们是一对多的关系。
Condition接口的出现就是为了拓展同步代码块中的wait/notify机制。
监视器锁的wait/notify的弊端
通常情况下,我们调用wait方法,主要是因为一定的条件没有满足。而另一方面,所有调用了wait方法的线程,都会在同一个监视器锁的waitset中等待,这看上去很合理,但是却是却是该机制的短板所在——所有的线程都等待在同一个notify()方法上。每一个调用wait方法的线程可能等待不同的条件,但是有时候即使自己等待的条件并没有满足,线程也有可能被别的线程的notify()方法唤醒,因为大家用的是同一个监视器锁。这样一来,即使自己被唤醒后,抢到了监视器锁,发现条件其实还是不满足,还是得调用wait()方法挂起,就导致了很多无意义的的时间和CPU资源的浪费。这一切的根源就在于我们在调用wait()方法时没有办法来指明究竟在等待什么样的条件,因此唤醒时,也不知道该唤醒谁,只能把该监视器锁上所有等待的线程都唤醒了。因此,最好的方式是,我们在挂起时就指定了在什么样的条件下挂起,同时,在等待的事件发生后,只唤醒等待在这个事件上的线程,而实现了这个思路的就是Condition接口。有了Condition接口,我们就可以在同一个锁上创建不同的唤醒条件,从而在一定条件满足后,有针对性的唤醒特定的线程,而不是一股脑的将所有等待的线程都唤醒。
Condition的await()/signal()机制
Object类的wait/notify机制
public class Object {
public final void wait() throws InterruptedException {
}
public final native void wait(long var1) throws InterruptedException;
public final void wait(long timeoutMillis, int nanos) throws InterruptedException {
}
public final native void notify();
public final native void notifyAll();
}
Condition接口
public interface Condition {
void await() throws InterruptedException;
void awaitUninterruptibly();
long awaitNanos(long var1) throws InterruptedException;
boolean await(long var1, TimeUnit var3) throws InterruptedException;
boolean awaitUntil(Date var1) throws InterruptedException;
void signal();
void signalAll();
}
对比
Object方法 | Condition方法 | 区别 |
void wait() | void await() | |
void wait(long timeout) | long awaitNanos(long timeout) | 时间单位 返回值 |
void wait(long timeout, int nanos) | boolean await(long time, TimeUnit unit) | 时间单位 参数类型 返回值 |
void notify() | void signal(); | |
void notifyAll() | void signalAll(); | |
void awaitUninterruptibly(); | Condition独有 | |
boolean awaitUntil(Date deadline); | Condition独有 |
- 其他线程调用了notify/signal方法,并且当前正好是被选中来唤醒的那一个
- 其他线程调用了notifyAll/signalAll方法
- 其他线程中断了当前线程
- 超时时间到了
其中,第三条会抛出InterruptedException,是比较容易分辨的;除去这个,当wait()方法返回后,我们其实无法区分它是超时时间到了返回了,还是被notify返回的。但是对于await()方法,因为它是有返回值的,我们就能通过返回值来区分:
- 如果awaitNanos(long timeout)的返回值大于0,说明超时时间还没到,则该返回是由signal行为导致的
- 如果await(long timeout, TimeUnit unit)返回true,说明超时时间还没到,则该返回是由signal行为导致的
前面介绍的所有的wait/await方法,它们方法的签名中都抛出了InterruptedException,说明它们在等待的过程中都是响应中断的,awaitUninterruptibly方法从名字中就可以看出,它在等待锁的过程中是不响应中断的,所以没有InterruptedException抛出。也就是说,它一直阻塞,直到signal/signalAll被调用。如果这过程中线程被中断了,它并不响应这个中断,只是在该方法返回的时候,该线程的中断标志位将是true,调用者可以检测这个中断标志位以辅助判断在等待过程中是否发生了中断,以此决定要不要做额外的处理。
boolean awaitUntil(Date deadline)和boolean await(long timeout, TimeUnit unit)其实作用是差不多的,返回值代表的含义也一样,只不过一个是相对时间,一个是绝对时间,awaitUntil方法的参数是Date,表示了一个绝对时间,即截止日期,在这个日期之前,该方法会一直等待,除非被signal或者被中断。