前言
在保证线程安全的机制 同步阻塞 中,加锁方式除了synchronized还有一个不同于它的,需要手动加锁解锁的方式:ReentrantLock类的实现。
一、介绍
ReentrantLock是jdk1.5新增的和synchronized一样可以达到同步互斥效果的一个可重入锁,它接口自Lock类。(可重入锁:可重复可递归调用的锁,在外层使用完锁之后,在内层仍然可以使用)
二、Lock接口
该接口是无条件的、可轮询的、定时的、可中断的锁获取操作。其中方法包括:
(1)lock():该方法用来获取锁,如果锁已经被其他线程获取,进行等待。
(2)lockInterruptibly():该方法用来获取锁,如果锁已经被其他线程获取,进行等待。等待途中允许其他线程调用interrupt()方法来中断等待,并抛出InterruptedException。
(3)tryLock():该方法用来获取锁,如果获取成功返回true,失败则返回false。
(4)tryLock(long time,TimeUnit unit):该方法用来获取锁,如果锁已经被其他线程获取,等待time长的时间,在时限内获取到锁就会返回true,时限过后还获取不到就会返回false。
(5)unlock():该方法用来释放锁。
三、实现
以一个简单的生消模型作为例子:
class SharingQue<E>{
private final LinkedList<E> queue = new LinkedList<>();
private static final int defaultMax = 5;
private final int capacity;
private ReentrantLock lock = new ReentrantLock();
private Condition isFull = lock.newCondition();//两个阻塞队列
private Condition isEmpty = lock.newCondition();
public SharingQue(){
this(defaultMax);
}
public SharingQue(int size){
this.capacity = size;
}
public void put(E value){
lock.lock();
try {
while(queue.size() >= capacity){
System.out.println(Thread.currentThread().getName()+":位子满了,放不进去");
isFull.await();
}
queue.addLast(value);
System.out.println(Thread.currentThread().getName()+":放入"+value);
isEmpty.signalAll();//让那些因为队列空拿不到东西的消费者们竞争出一个来拿
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void take(){
lock.lock();
try {
while(queue.isEmpty()){
System.out.println(Thread.currentThread().getName()+":没有东西,无法消费");
isEmpty.await();
}
E thing = queue.removeFirst();
System.out.println(Thread.currentThread().getName()+":拿走"+thing);
isFull.signalAll();//因没位子而阻塞生产者们竞争出一个可以来放了
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
可以看出:
(1)Condition对象可以用来当做阻塞队列,并且个数不再囿于一个,可以用多个阻塞队列来存放因不同原因阻塞的线程。
(2)加锁解锁必须要手动进行,并且unlock()有可能还执行不到,因为finally有时也会被忘记,这是他的一个缺陷。
(3)在put()写操作完成后,因队列空而阻塞住的消费者就可以来竞争着消费了,用notifyAll()会更加公平,也会避免死锁问题的发生;在take()读操作完成后,因队列满而阻塞住的生产者也可以来竞争着放产品了。
四、原理
看源码:首先,根据Sync类(继承至AbstractQueuedSynchronizer类)创建一个同步控制的基础sync,后面分成不公平和公平两个版本:NonfairSync和FairSync,两者都是ReentrantLock的静态内部类,而ReentrantLock的很多方法也都是它们代为实现的。
在构造函数中:如果new ReentrantLock()不传参,就默认创建一个不公平排序策略的对象,即直接用CAS占有锁,不管有没有其他等待的线程;如果传了boolean参数,参数是true就创建一个公平排序策略的对象,false就创建不公平排序策略对象。
而调用方法的时候,就根据sync是FairSync还是NonFairSync的对象来调用不同的方法。