相对于synchronized它具备以下特点
- 可中断
- 可以设置超时时间
- 可以设置为公平锁
- 支持多个条件变量
与synchronized一样,都支持可重入
基本语法
reentrantLock.lock();
try{
// 临界区
} finally {
// 释放锁
reentrantLock.unlock();
}
可重入
可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权再次获得这把锁
如果是不可冲入锁,那么第二次获得锁时,自己也会被锁挡住
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
method1();
}
public static void method1() {
lock.lock();
try {
log.debug("execute method1");
method2();
} finally {
lock.unlock();
}
}
public static void method2() {
lock.lock();
try {
log.debug("execute method2");
method3();
} finally {
lock.unlock();
}
}
public static void method3() {
lock.lock();
try {
log.debug("execute method3");
} finally {
lock.unlock();
}
}
输出
2022/03/06-22:23:07.927 [main] c.Test1 - execute method1
2022/03/06-22:23:07.927 [main] c.Test1 - execute method2
2022/03/06-22:23:07.927 [main] c.Test1 - execute method3
可打断
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("start..");
try {
lock.lockInterruptibly();
log.debug("获得了锁");
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("interrupt..");
return;
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("lock get");
t1.start();
try {
Thread.sleep(1000);
t1.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
输出
2022/03/06-22:28:23.876 [main] c.Test2 - lock get
2022/03/06-22:28:23.876 [t1] c.Test2 - start..
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at reentrantlocktest.Test2.lambda$main$0(Test2.java:20)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "t1" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
at reentrantlocktest.Test2.lambda$main$0(Test2.java:27)
at java.lang.Thread.run(Thread.java:748)
2022/03/06-22:28:24.889 [t1] c.Test2 - interrupt..
注意如果是不可中断模式,那么及时使用了interrupt也不会让等待中断
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("start..");
try {
lock.lock();
log.debug("lock get");
return;
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("lock get");
t1.start();
try {
Thread.sleep(1000);
t1.interrupt();
log.debug("interrupt");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
log.debug("unlock");
}
}
输出
2022/03/06-22:31:12.715 [main] c.Test2 - lock get
2022/03/06-22:31:12.715 [t1] c.Test2 - start..
2022/03/06-22:31:13.715 [main] c.Test2 - interrupt
2022/03/06-22:31:13.715 [main] c.Test2 - unlock
2022/03/06-22:31:13.715 [t1] c.Test2 - lock get
锁超时
立刻失败
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("start..");
if (!lock.tryLock()) {
log.debug("lock get fail!!");
return;
}
try {
log.debug("lock get");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("lock get");
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
输出
2022/03/06-22:33:54.606 [main] c.Test4 - lock get
2022/03/06-22:33:54.608 [t1] c.Test4 - start..
2022/03/06-22:33:54.608 [t1] c.Test4 - lock get fail!!
超时失败
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("start..");
try {
if (!lock.tryLock(1, TimeUnit.SECONDS)) {
log.debug("lock get fail!!");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
log.debug("lock get");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("lock get");
t1.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
输出
2022/03/06-22:35:15.945 [main] c.Test4 - lock get
2022/03/06-22:35:15.945 [t1] c.Test4 - start..
2022/03/06-22:35:16.958 [t1] c.Test4 - lock get fail!!
使用tryLock解决哲学家就餐问题
public class Chopstick extends ReentrantLock {
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "Chopstick{" +
"name='" + name + '\'' +
'}';
}
}
public class Philosoper extends Thread {
Chopstick left;
Chopstick right;
public Philosoper(String name, Chopstick left, Chopstick right) {
super(name);
this.left = left;
this.right = right;
}
public void eat() {
try {
log.debug("eating");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true) {
if (left.tryLock()) {
try {
if (right.tryLock()) {
try {
eat();
} finally {
right.unlock();
}
}
} finally {
left.unlock();
}
}
}
}
}
公平锁
ReentrantLock默认是不公平的
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock(false);
lock.lock();
for (int i = 0; i < 500; i++) {
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "running");
// log.debug("running");
} finally {
lock.unlock();
}
}, "t" + i).start();
}
Thread.sleep(200);
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "running");
// log.debug("running");
} finally {
lock.unlock();
}
}, "强制进入").start();
lock.unlock();
}
注意
该实验不一定总能复现
输出
t0running
t31running
t4running
强制进入running
t5running
t8running
改为公平锁后
ReentrantLock lock = new ReentrantLock(true);
总是在最后输出
t497running
t498running
t499running
强制进入running
条件变量
synchronized中也有条件变量,就是我们讲原理时那个WaitSet休息室,当条件不满足时进入WaitSet等待
ReentrantLock的条件变量比synchronized强大之处在于,它是支持多个条件变量的,这就好比
- synchronized是那些不满足条件的线程都在一间休息室等消息
- 而ReentrantLock支持多间休息室,有专门等烟的休息室,专门等早餐的休息室,唤醒时也是按休息室来唤醒
使用要点:
- await 前要获得锁
- await 执行后,会释放锁,进入conditionObject等待
- await 的线程被唤醒(或打断、或超时),重新竞争lock锁
- 竞争lock锁成功后,从awit后继续执行
例:
static ReentrantLock lock = new ReentrantLock();
static Condition waitCigaretteQueue = lock.newCondition();
static Condition waitBreakfastQueue = lock.newCondition();
static volatile boolean hasCigarette = false;
volatile static boolean hasBreakfast = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
try {
lock.lock();
while (!hasCigarette) {
try {
waitCigaretteQueue.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("waited cigarette");
} finally {
lock.unlock();
}
}, "t1").start();
new Thread(() -> {
try {
lock.lock();
while (!hasBreakfast) {
try {
waitBreakfastQueue.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("waited breakfast");
} finally {
lock.unlock();
}
}, "t2").start();
Thread.sleep(1000);
sendCigarette();
Thread.sleep(1000);
sendBreakFast();
}
public static void sendCigarette() {
lock.lock();
try {
log.debug("cigarette arrive");
hasCigarette = true;
waitCigaretteQueue.signal();
} finally {
lock.unlock();
}
}
public static void sendBreakFast() {
lock.lock();
try {
log.debug("breakfast arrive");
hasBreakfast = true;
waitBreakfastQueue.signal();
} finally {
lock.unlock();
}
}
输出
2022/03/06-23:09:03.015 [main] c.Test6 - cigarette arrive
2022/03/06-23:09:03.015 [t1] c.Test6 - waited cigarette
2022/03/06-23:09:04.017 [main] c.Test6 - breakfast arrive
2022/03/06-23:09:04.017 [t2] c.Test6 - waited breakfast