线程的同步控制,最基础的就是synchronized关键字。但是在使用的过程中有一些限制,并没有那么的灵活。
可重入
所以下面介绍一下ReentrantLock的使用和相关特性,一个简单的demo如下:
上面的demo创建了两个线程 t1、t2,并且同时使用了lock作为锁,完成了i++的操作。这里面需要注意的是,lock.lock()调用了几次,响应的lock.unlock()就应该调用几次。并且无论执行体如何,最终都要调用lock.unlock()。
响应中断
在两个线程同时抢占同一个锁资源的时候,很有可能产生死锁的现象。ReentrantLock可以支持中断响应,从而规避死锁的产生。
从上面代码可以看出来,此时使用的是lock.lockInterruptibly(),而不是lock.lock()。当使用lock.lockInterruptibly()加锁等待的时候,可以响应中断,从而使线程对出死锁状态。
上面代码一共起了两个线程,故意造成了死锁的状态。此时在主线程中调用了中断方法,此时t2线程对出等待,释放锁资源,从而可以让t1继续执行下去。
超时限制
当多线程对同一监视器对象加锁时,如果超过一定时间还没获得锁,则立即返回false。这里使用到的是tryLock()。
上述代码启动了2个线程,在5S内必定一个线程获取锁对象失败。
另外tryLock()不带参的方法,表示对当前对象加锁,如果获取不到则立即返回false。通过这个特性,可以在程序中用无限循环,使其不断的去尝试加锁。
公平锁性
在大部分的应用场景下,锁的申请获取都是不公平的。但是我们通过ReentrantLock可以构造出公平的锁对象。
使用上述方法可以构造出公平锁出来,我们通常默认的fair都是false。公平锁本身会维护一个有序队列,开销相对较高。
公平锁实现
ReentrantLock 的公平锁和非公平锁都委托了 AbstractQueuedSynchronizer#acquire 去请求获取。
公平锁FairSync
公平锁的实现在于,当有线程获取锁资源时, 都会检查有没有等待队列。如果有,则按先进先出的原则进行。
非公平锁NonfairSync
加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待。
非公平锁性能比公平锁高5~10倍,因为公平锁需要在多核的情况下维护一个队列。
下面看一个公平锁的测试Demo
通过运行上述程序可以看出,锁的获取都是有序的,公平的。
综上所述
一般来说,根据JVM的调度,系统更倾向于把锁给当前已经获得锁的线程。从而减少线程频繁切换带来的系统消耗。
对于ReentrantLock主要有以下几个方法:
- lock():获取锁,如果已被占用,则等待;
- lockInterruptibly():获得锁,但是响应中断;
- tryLock():获得锁,如可以立即获得,则返回true,如不可以,则立即返回false;
- tryLock(long time, TimeUnit unit):超时未获得锁,则返回;
- unlock():释放锁;