vlambda博客
学习文章列表

可重入互斥锁ReentrantLock

ReentrantLock是Java并发包中可重入互斥锁,其基本行为和语义与使用synchronized方法和语句访问的隐式监视锁相同,但扩展性更强。

一个ReentrantLock锁由上次成功锁定但尚未解除锁定的线程拥有。当锁不属于另一个线程时,调用lock()的线程将返回并成功获取锁。如果当前线程已经拥有锁,则方法将立即返回。这可以使用方法isheldbycurrenthread和getHoldCount来检查。

除了实现Lock接口之外,这个类还定义了一些方法来检查锁的状态。此锁支持同一线程最多2147483647个递归锁。尝试超过此限制将导致锁定方法抛出Error。


类定义

public class ReentrantLock  implements Lock, java.io.Serializable

Lock实现类提供了比使用synchronized方法和语句更广泛的锁定操作。它们允许更灵活的结构,可能具有完全不同的属性,并且可能支持多个关联的Condition对象。


构造方法

/** * 默认创建非公平锁 */public ReentrantLock() { sync = new NonfairSync();}/** * 使用给定的公平性策略创建ReentrantLock实例。 */public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync();}

公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即FIFO先进先出顺序。非公平锁就是一种获取锁的抢占机制,是随机获得锁的,先来的不一定先得到锁,这个方式可能造成某些线程一直拿不到锁,结果也就是不公平的了。当然这里的顺序并不是调用线程start()方法的顺序。


基本使用格式

ReentrantLock lock = new ReentrantLock(); // 获取锁lock.lock();try { // 业务逻辑} finally { // 释放锁 lock.unlock();}

ReentrantLock的锁功能主要是通过继承了AbstractQueuedSynchronizer的内部类Sync来实现的,AbstractQueuedSynchronizer(AQS)是一种提供了原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架。


基本应用

输出结果:

可重入互斥锁ReentrantLock

从运行结果看,当前线程打印完毕之后释放锁之后,其他线程才可以打印,线程打印的数据是分组打印的,因为当前线程已经持有锁,但线程之间的打印顺序还是随机的。


Condition

关键字synchronized与wait()和notify()/notifyAll()方法相结合可以实现等待/通知模式,类RentrantLock也可以实现相同的功能,但需要借助于Condition对象。Condition类是在JDK5中出现的技术,使用它有更好的灵活性,比如可以实现多路通知功能,也就是在一个Lock对象里面可以创建多个Condition实例,线程对象可以注册在指定的Condition中,从而可以有选择性地进行线程通知,在调度线程上更加灵活。

在使用notify()/notifyAll()方法进行通知时,被通知的线程是由JVM随机选择的。但使用ReentrantLock结合Condition类是可以实现选择性通知的,而synchronized就相当于整个Lock对象中只有一个单一的Condition对象,所有的线程都注册在它一个对象的身上。线程开始notifyAll()时,需要通知所有的waiting线程,没有选择权,会出现相当大的效率问题。

public interface Condition

接口中的方法也比较简单就2个功能:等待和唤醒。

可重入互斥锁ReentrantLock

ReentrantLock中调用newCondition()返回的是AbstractQueuedSynchronizer中的ConditionObject。

可重入互斥锁ReentrantLock


await()

可重入互斥锁ReentrantLock

输出结果:

可重入互斥锁ReentrantLock

只打印出了“锁上了”,而且程序并没有结束运行,说明线程进入等待状态了。
signal()

可重入互斥锁ReentrantLock

输出结果:

可重入互斥锁ReentrantLock



多个Condition实现部分通知

可重入互斥锁ReentrantLock

可重入互斥锁ReentrantLock


输出结果:

从运行结果上看,调用signalAll_A()方法之后只唤醒了等待中的线程A,而线程B并没有被唤醒,所以程序并没有结束。