JUC中的 ReentrantLock
Posted XeonYu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC中的 ReentrantLock相关的知识,希望对你有一定的参考价值。
上一篇:
java.util.concurrent.locks包中的接口和实现类
ReentrantLock (可重入锁)
可重入锁就是当一个线程已经获取到锁,可以再次获取这个锁,注意是同一个线程,synchronized也是可重入锁,文章末尾会在代码中看一下这个概念。
ReentrantLock 跟synchronized不同,synchronized是自动上锁,自动解锁的,而Lock接口的实现类是需要手动上锁,手动解锁。
先看一下ReentrantLock中的方法,常用的其实就是对Lock接口实现的方法。
我们先来简单用一用,还是之前买票的例子:
lock()
获取锁,如果锁被其他线程持有,则处于阻塞状态,一直等待直到获取锁
class Ticket {
public static int totalCount = 30;
private static int number = 1;
/*重入锁*/
private final static ReentrantLock reentrantLock = new ReentrantLock();
/*卖票*/
public static void sale() {
/*获取锁*/
reentrantLock.lock();
try {
if (totalCount == 0) {
System.out.println("票卖完了");
return;
}
totalCount--;
System.out.println(Thread.currentThread().getName() + "卖出了第" + number + "张票,剩余" + totalCount + "张票");
number++;
} catch (Exception e) {
e.printStackTrace();
} finally {
/*释放锁*/
reentrantLock.unlock();
}
}
}
main方法如下:
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(() -> {
while (Ticket.totalCount > 0) {
Ticket.sale();
}
}, "窗口" + i).start();
}
}
看下运行结果:
可以看到,数据是ok的,但是只有一个线程在工作,其他线程都没有获取到锁,我们之前用synchronized的时候,是在加锁之前使用了sleep来让其他线程能拿到锁,得到执行机会。
但是用ReentrantLock就不用这样写了,来看下ReentrantLock的构造方法:
两个构造,其中一个构造方法接受一个布尔值,如果传入true,则表示当前锁是一个公平锁,false就是非公平锁
公平锁:
获取锁是按照加锁的顺序来获取的,排队,永远是队列的第一位获取锁。
非公平锁:
获取锁是抢占机制,可能出现一个线程一直获取锁,其他线程一直获取不到锁的现象。默认的是非公平锁
我们把上面的代码的构造中传入true再来看一下:
运行结果如下:
可以看到,每个线程都得到了执行机会。
tryLock()
尝试立刻获取锁,如果到了就返回true,否则就返回false。跟lock方法的区别就是不会一直等待下去,获取不到锁就可以做一些其他操作。
举个例子,去Tony老师店里剪头发,如果去了不用排队就能剪,那就直接剪,如果去了发现tony正在给别人剪,那就不剪了,下次再剪。
简单用一用:
class Tony {
private final ReentrantLock reentrantLock = new ReentrantLock();
/*剪头发*/
public void cutHair() {
String name = Thread.currentThread().getName();
System.out.println(name + "进店剪头发");
if (reentrantLock.tryLock()) {
try {
System.out.println(name + "排上队了,开始剪头发。。。");
TimeUnit.SECONDS.sleep(3);
System.out.println(name + "头发剪完了,美滋滋。。。");
} catch (Exception e) {
e.printStackTrace();
} finally {
/*释放锁*/
reentrantLock.unlock();
}
} else {
/*没获取到锁*/
System.out.println(name + "看到tony老师正在给别人剪头发,很难受,不等了,下次再剪");
}
}
}
main方法:
public static void main(String[] args) {
Tony tony = new Tony();
new Thread(tony::cutHair, "yzq").start();
new Thread(tony::cutHair, "xeon").start();
}
运行结果如下:
tryLock(long time, TimeUnit unit)
跟tryLock类似,只是多了个获取锁的超时时间,在指定时间段内获取到锁就返回true,否则返回false。
还是上面那个例子,去Tony老师店里剪头发,如果去了不用排队就能剪,那就直接剪,如果去了发现tony正在给别人剪,那就等2秒钟,2秒钟还没剪完,那就不等了,下次再剪。
class Tony {
private final ReentrantLock reentrantLock = new ReentrantLock();
/*剪头发*/
public void cutHair() {
String name = Thread.currentThread().getName();
System.out.println(name + "进店剪头发");
try {
if (reentrantLock.tryLock(2, TimeUnit.SECONDS)) {
System.out.println(name + "排上队了,开始剪头发。。。");
TimeUnit.SECONDS.sleep(3);
System.out.println(name + "头发剪完了,美滋滋。。。");
} else {
/*没获取到锁*/
System.out.println(name + "看到tony老师正在给别人剪头发,等了2秒钟还没剪完那就不等了,下次再剪");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
/*判断是否是当前线程持有锁 是的话释放 否则不用处理*/
if (reentrantLock.isHeldByCurrentThread()) {
reentrantLock.unlock();
}
}
}
}
唯一需要注意的是在finally 中释放锁的话,需要加个判断,是否是当前线程持有锁 :isHeldByCurrentThread,是的话才能释放锁,否则会报
IllegalMonitorStateException异常。
运行结果:
好了 ReentrantLock常用的方法就是这些。
代码理解什么是可重入锁
文章开头我们说了可重入锁的概念,这里我们用代码看一下 可重入
是如何体现出来的。
代码如下:
class MyReentrantLock {
private final ReentrantLock lock = new ReentrantLock();
public void fun1() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "持有当前锁的次数:" + lock.getHoldCount());
fun2();
System.out.println(Thread.currentThread().getName() + "持有当前锁的次数:" + lock.getHoldCount());
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + "持有当前锁的次数:" + lock.getHoldCount());
}
}
public void fun2() {
lock.lock();
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "持有当前锁的次数:" + lock.getHoldCount());
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + "持有当前锁的次数:" + lock.getHoldCount());
}
}
}
上面两个方法,每个方法都有加锁解锁的过程,且fun1中调用了fun2,getHoldCount方法用来查询当先线程持有当前锁的次数
来看下main方法
public static void main(String[] args) {
MyReentrantLock myReentrantLock = new MyReentrantLock();
new Thread(myReentrantLock::fun1, "线程1").start();
new Thread(myReentrantLock::fun2, "线程2").start();
}
起了两个线程,分别调用fun1和fun2.看下运行结果:
可以看到,线程1调用的是fun1,先在fun1中加了一次锁,然后又调用了fun2,在fun2中又加了一次锁,所以,线程1加锁的次数是两次。直到线程1把2次锁全部释放后,线程2才有机会执行。
总结一下:同一个线程可以多次获取同一个锁,这个锁就是可重入锁。
如果你觉得本文对你有帮助,麻烦动动手指顶一下,可以帮助到更多的开发者,如果文中有什么错误的地方,还望指正,转载请注明转自喻志强的博客 ,谢谢!
以上是关于JUC中的 ReentrantLock的主要内容,如果未能解决你的问题,请参考以下文章
重点知识学习(8.3)--[JUC常用类 || Java中的14把锁 || 对象头 || Synchronized 与 ReentrantLock]
JUC并发编程 共享模式之工具 JUC ReentrantLock -- ReentrantLock原理
Java——聊聊JUC中的锁(synchronized & Lock & ReentrantLock)