Java中ReentrantLock的使⽤
⼀、基本概念和使⽤
可重⼊锁: 也叫做递归锁,指的是同⼀线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。
在JAVA中ReentrantLock 和synchronized 都是可重⼊锁;
重⼊锁ReentrantLock 相对来说是synchronized、Object.wait()和ify()⽅法的替代品(或者说是增强版),在JDK5.0的早期版本,重⼊锁的性能远远好于synchronized,但从JDK6.0开始,JDK在synchronized上做了⼤量的优化,使得两者的性能差距并不⼤。但ReentrantLock也有⼀些synchronized没法实现的特性。
ReentrantLock 在Java也是⼀个基础的锁,ReentrantLock 实现Lock接⼝提供⼀系列的基础函数,开发⼈员可以灵活的是应⽤函数满⾜各种复杂多变应⽤场景;
1.Lock接⼝:
Java中的ReentrantLock 也是实现了Java中锁的核⼼接⼝Lock,在Lock接⼝定义了标准函数,但是具体实现是在实体类中[类似List和ArrayList、LinkedList关系];
//获取锁,获取不到lock就不罢休,不可被打断,即使当前线程被中断,线程也⼀直阻塞,直到拿到锁,⽐较⽆赖的做法。
void lock();
/**
*获取锁,可中断,如果获取锁之前当前线程被interrupt了,
*获取锁之后会抛出InterruptedException,并且停⽌当前线程;
*优先响应中断
美国现在什么季节
*/
void lockInterruptibly() throws InterruptedException;
//⽴即返回结果;尝试获得锁,如果获得锁⽴即返回ture,失败⽴即返回fal
boolean tryLock();
//尝试拿锁,可设置超时时间,超时返回fal,即过时不候
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//释放锁
void unlock();
//返回当前线程的Condition ,可多次调⽤
Condition newCondition();
2.ReentrantLock私有public⽅法
ReentrantLock中除了实现Lock中定义的⼀些标准函数外,同时提供其他的⽤于管理锁的public⽅法:
//传⼊boolean值,true时create⼀个公平锁,fal为⾮公平锁
ReentrantLock(boolean fair)
//查看有多少线程等待锁
int getQueueLength()
/
/是否有线程等待抢锁
boolean hasQueuedThreads()
//是否有指定线程等待抢锁
boolean hasQueuedThread(Thread thread)
//当前线程是否抢到锁。返回0代表没有
int getHoldCount()
//查询此锁是否由任何线程持有
boolean isLocked()
//是否为公平锁
boolean isFair()
3.ReentrantLock中Condition的使⽤
渡浙江问舟中人
ReentrantLock中另⼀个重要的应⽤就是Condition,Condition是Lock上的⼀个条件,可以多次newCondition()获得多个条
件,Condition可⽤于线程间通信,通过Condition能够更加精细的控制多线程的休眠与唤醒,⽽且在粒度和性能上都优于Object的通信⽅法(wait、notify 和 notifyAll);
还时先看⼀下Condition 接⼝的源码吧:
public interface Condition {
/**
*Condition线程进⼊阻塞状态,调⽤signal()或者signalAll()再次唤醒,
*允许中断如果在阻塞时锁持有线程中断,会抛出异常;
*重要⼀点是:在当前持有Lock的线程中,当外部调⽤会await()后,ReentrantLock就允许其他线程来抢夺锁当前锁,
*注意:通过创建Condition对象来使线程wait,必须先执⾏lock.lock⽅法获得锁隋朝开国皇帝
*/
void await() throws InterruptedException;
//Condition线程进⼊阻塞状态,调⽤signal()或者signalAll()再次唤醒,不允许中断,如果在阻塞时锁持有线程中断,继续等待唤醒
void awaitUninterruptibly();
//设置阻塞时间,超时继续,超时时间单位为纳秒,其他同await();返回时间⼤于零,表⽰是被唤醒,等待时间并且可以作为等待时间期望值,⼩于零表⽰超时
long awaitNanos(long nanosTimeout) throws InterruptedException;
//类似awaitNanos(long nanosTimeout);返回值:被唤醒true,超时fal
boolean await(long time, TimeUnit unit) throws InterruptedException;
//类似await(long time, TimeUnit unit)
boolean awaitUntil(Date deadline) throws InterruptedException;
//唤醒指定线程
void signal();
//唤醒全部线程
void signalAll();
}
ReentrantLock.Condition的线程通信:
ReentrantLock.Condition是在粒度和性能上都优于Object的notify()、wait()、notifyAll()线程通信的⽅式。
Condition中通信⽅法相对Object的通信在粒度上是粒度更细化,表现在⼀个Lock对象上引⼊多个Condition监视器、通信⽅法中除了和Object对应的三个基本函数外,更是新增了线程中断、阻塞超时的函数;
Condition中通信⽅法相对Object的通信在性能上更⾼效,性能的优化表现在ReentrantLock⽐较synchronized的优化 ;
基本⽰例代码:
多线程通信/同步的⼀个经典的应⽤属于**⽣产者消费者模式**,关于通过ReentrantLock的newCondition()是实现⽣产者消费者模式可以直接参考:
ReentrantLock.Condition线程通信注意点:
1.使⽤**ReentrantLock.Condition的signal()、await()、signalAll()⽅法使⽤之前必须要先进⾏lock()操作**[记得unlock()],类似使⽤Object的
notify()、wait()、notifyAll()之前必须要对Object对象进⾏synchronized操作;否则就会抛IllegalMonitorStateException;
2.注意在使⽤**ReentrantLock.Condition中使⽤signal()、await()、signalAll()⽅法,不能和Object的notify()、wait()、notifyAll()⽅法混⽤,否则
抛出IllegalMonitorStateException`;
4.公平锁与⾮公平锁
公平锁: 是指多个线程竞争同⼀资源时[等待同⼀个锁时],获取资源的顺序是按照申请锁的先后顺序的;公平锁保障了多线程下各线程获取锁的顺序,先到的线程优先获取锁,有点像早年买⽕车票⼀样排队早的⼈先买到⽕车票;
基本特点: 线程执⾏会严格按照顺序执⾏,等待锁的线程不会饿死,但 整体效率相对⽐较低;
⾮公平锁: 是指多个线程竞争同⼀资源时,获取资源的顺序是不确定的,⼀般是抢占式的;⾮公平锁相对公平锁是增加了获取资源的不确定性,但是整体效率得以提升;
基本特点: 整体效率⾼,线程等待时间⽚具有不确定性;
公平锁与⾮公平锁的测试demo:
重⼊锁ReentrantLock实现公平锁和⾮公平锁很简单的,因为ReentrantLock构造函数中可以直接传⼊⼀个boolean值fair,对公平性进⾏设置。当fair为true时,表⽰此锁是公平的,当fair为fal时,表⽰此锁是⾮公平的锁;
来个简单的demo;
public static void main(String[] args) {
ExecutorService threadPool = wCachedThreadPool();
ReentrantLock fairLock = new ReentrantLock(true);
ReentrantLock unFairLock = new ReentrantLock();
for (int i = 0; i < 10; i++) {
threadPool.submit(new TestThread(fairLock,i," fairLock"));
threadPool.submit(new TestThread(unFairLock, i, "unFairLock"));
}
千足金和足金的区别}
static class TestThread implements Runnable {
Lock lock;
int indext;
String tag;
public TestThread(Lock lock, int index, String tag) {
this.lock = lock;
江米面炸糕的做法this.indext = index;
this.tag = tag;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getId() + " 线程 START " + tag);
meath();
}
private void meath() {
lock.lock();
try {
if((indext&0x1)==1){
Thread.sleep(200);
}el{
Thread.sleep(500);
}
冰皮月饼怎么做System.out.println(Thread.currentThread().getId() + " 线程获得: Lock ---》" + tag + " Index:" + indext);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
⼆、ReentrantLock与synchronized简单对⽐
ReentrantLock是JDK1.5之后引⼊的,synchronized作为关键字在ReentrantLock引⼊后进⾏的⼤量修改性能不断提升;
1.可重⼊性
ReentrantLock和synchronized都具有可重⼊性,写代码synchronized更简单,ReentrantLock需要将lock()和unlock()进⾏⼀⼀对应否则有死锁的风险;
2.锁的实现⽅式
Synchronized作为Java关键字是依赖于JVM实现的,⽽ReenTrantLock是JDK实现的,有什么区别,说⽩了就类似于操作系统来控制实现和⽤户⾃⼰敲代码实现的区别。前者的实现是⽐较难见到的,后者有直接的源码可供阅读。
3.公平性
ReentrantLock提供了公平锁和⾮公平锁两种API,开发⼈员完全可以根据应⽤场景选择锁的公平性;
synchronized是作为Java关键字是依赖于JVM实现,Java团队应该是优先考虑性能问题,因此synchronized是⾮公平锁。
⼩插曲
之前看了很多博⽂有些⼈说synchronized是公平锁有⼈说是⾮公平锁,总之,看到让⼈的苦笑不得,于是⾃⼰测试⼀下[JDK1.8]测试代码如下,结果很明显synchronized就是⼀中⾮公平锁。
public synchronizedTest() {
for(int i=0;i<20;i++){
走廊的英语int finalI = i;
new Thread(() ->
test(finalI)
).start();
}
}
synchronized private void test(int index) {
System.out.println("--------------- > Task :" + index);
}
}
4.⼆者性能和粒度
Java⼀直被诟病的就是性能问题,所以这是⼀个很重要的问题。
在Synchronized优化以前,synchronized的性能是⽐ReenTrantLock差很多的,但是⾃从Synchronized引⼊了偏向锁,轻量级锁(⾃旋锁)后,两者的性能就差不多了,在两种⽅法都可⽤的情况下,官⽅甚⾄建议使⽤synchronized,其实synchronized的优化我感觉就借鉴了ReenTrantLock中的CAS技术。都是试图在⽤户态就把加锁问题解决,避免进⼊内核态的线程阻塞;
⾄于⼆者的细粒度差别就更明显了,Synchronized只是关键字,⽽ReentrantLock则提供较为多样的实现⽅式和更多的功能;
5.编程灵活度和难度
根据上⾯的介绍估计这个问题已近很明确了;
很明显Synchronized的使⽤⽐较⽅便简洁,并且由编译器去保证锁的加锁和释放,⽽ReenTrantLock需要⼿⼯声明来加锁和释放锁,为了避免忘记⼿⼯释放锁造成死锁,所以最好在finally中声明释放锁。其中ReenTrantLock使⽤不当死锁问题更是让⼈头痛不已。
灵活度:很明显ReentrantLock优于synchronized;
难度:也很明显ReentrantLock难于synchronized;
ReenTrantLock实现的原理:
在⽹上看到相关的源码分析,本来这块应该是本⽂的核⼼,但是感觉⽐较复杂就不⼀⼀详解了,简单来说,ReenTrantLock的实现是⼀种⾃旋锁,通过循环调⽤CAS操作来实现加锁。它的性能⽐较好也是因为避免了使线程进⼊内核态的阻塞状态。想尽办法避免线程进⼊内核的阻塞状态是我们去分析和理解锁设计的关键钥匙。
什么情况下使⽤ReenTrantLock:
答案是,如果你需要实现ReenTrantLock的三个独有功能时
三、ReentrantLock实际开发中的应⽤场景
1.公平锁,线程排序执⾏,防饿死应⽤场景;
公平锁原则必须按照锁申请时间上先到先得的原则分配机制场景;
1).实现逻辑 上(包括:软件中函数计算、业务先后流程;硬件中操作实现中顺序逻辑)的顺序排队机制的场景;
软件场景:⽤户交互View中对⽤户输⼊结果分析类,分析过程后⾯算法依赖上⼀步结果的场景,例如:推荐算法实现[根据性别、年龄筛选]、阻塞队列的实现;古装人物画
硬件场景:需要先分析确认⽤户操作类型硬件版本或者⼚家,然后发出操作指令;例如:⾃动售货机;
2).现实 ⽣活中 时间排序的 公平原则:例如:客服分配,必须是先到先服务,不能出现饿死现象;
公平锁实现见上⽂:公平锁与⾮公平锁的测试demo:
逻辑代码实现那就没法⼦实现了;
阻塞队列的实现就是时间上的公平原则。
⽰例代码:没有