Lock实现锁的原理:
通过Node把线程构成队列,搁置除头结点外的其他线程,当头结点释放锁时唤醒下一个节点,同时更新头结点。
一、Condition接口
1、Condition接口:条件对象,阻塞和唤醒线程的作用。位于java.util.concurrent.locks包下,其中有await()、signal()、signalAll()等抽象方法,只有AQS和AQLS类中有内部类实现了Condition接口。
2、ReentrantLock接口中,有内部类Sync继承自AbstractQueuedSynchronizer抽象类,AQS中又有内部类ConditionObject实现了Condition接口。(各种使用内部类包一层,可能是出于安全的考虑.不直接用ReentrantLock继承AQS能防止AQS中的api被误用)
3、ReentrantLock的对象可以调用newCondition()这个final方法,创建一个AQS中内部类ConditionObject的对象来实现阻塞唤醒。 在LinkedBlockingQueue、LinkedBlockingDeque、ArrayBlockingQueue等阻塞队列中,其内部实现都是通过ReentrantLock创建Condition对象来实现阻塞和唤醒的。
4、AQS中维护的队列是当前等待资源的队列,Condition中维护的队列是等待signal的队列。
5、await():阻塞线程。类似Object类的wait()
1.将线程包装成Node节点,添加到Condition的队列;
2.主动释放锁,并自循环等待锁;
3.获取到锁,清理节点与等待队列的联系;
signal()、signalAll():发信号,唤醒线程。类似Object中的notify()、notifyAll()
将节点从Condition的等待队列移动到AQS阻塞队列
6、Condition对比Object的wait等方法:
1.Object是java底层的,Condition是语言级别的,可控性和扩展性更高;
2.Condition支持无响应中断、支持设置超时时间;
3.Condition支持多个等待队列(new多个Condition对象即可),Object方式只有一个。
二、阻塞队列
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.*;
public class MyBlockingQueue {
/** 存储数据的容器、总容量、当前size */
private List<Integer> data = new ArrayList<>();
private volatile int capacity;
private volatile int size;
/** 锁和Condition */
private Lock lock = new ReentrantLock();
private final Condition isFull = lock.newCondition();
private final Condition isNull = lock.newCondition();
MyBlockingQueue(int capacity) {
this.capacity = capacity;
}
/** 放数据 */
public void put(int n) {
try {
lock.lock();
try {
while (size >= capacity) {
System.out.println("--------队列满了,阻塞写");
isFull.await(); // 阻塞写入
}
} catch (Exception e) {
isFull.signal();
}
size++;
data.add(n);
isNull.signal(); // 写入成功,唤醒阻塞读
} finally {
lock.unlock();
}
}
/** 取数据 */
public int take() {
try {
lock.lock();
try {
while (size == 0) {
System.out.println("--------队列空了,阻塞读");
isNull.await(); // 阻塞读取
}
} catch (Exception e) {
isNull.signal();
}
size--;
int res = data.get(0);
data.remove(0);
isFull.signal(); // 读取成功,唤醒阻塞写
return res;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
MyBlockingQueue queue = new MyBlockingQueue(5);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
queue.put(i);
System.out.println("塞入" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
for (; ; ) {
System.out.println("消费"+queue.take());
try {
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
}
}