特点
主要是与synchronized
进行比较,它具有如下特点 :
- 可中断。只要加锁后,别的线程就不能中断synchronized的加锁,只能等它执行完成。
- 可以设置超时时间。规定时间内,如果ReentrantLock没有获得锁,那就放弃,不锁了x
- 可以设置公平/非公平
- 支持多个条件变量。精准唤醒。
基本语法:
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();
}
}
可打断
lock.lockInterruptibly()
表示这个加锁是可以被打断的,如果使用lock.lock()
是不可以被打断的。
如果没有竞争,这个方法可以获得lock对象锁。
如果有竞争,就进入阻塞队列,可以被其它线程用interrupt方法打断 。
下面展示的情况就是有竞争的时候,t1线程被阻塞,然后被main打断,抛出InterruptedException。
(注,如果是用lock(),即使interrupt也不会让等待中断。)
public static void canInterrupt() {
Thread t1 = new Thread(() -> {
log.debug("启动...");
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("等锁的过程中被打断");
return;
}
try {
log.debug("获得了锁");
} finally {
lock.unlock();
}
}, "t1");
// 这里已经被main线程加锁了
lock.lock();
log.debug("获得了锁");
t1.start();
try {
sleep(1);
t1.interrupt();
log.debug("执行打断");
} finally {
lock.unlock();
}
}
锁超时
lock.tryLock()
,尝试获取锁,获取不到,马上返回。
lock.tryLock(1, TimeUnit.SECONDS)
,尝试获取锁,1s后还获取不到,马上返回。
public static void timeLimit(){
Thread t1 = new Thread(() -> {
log.debug("启动...");
try {
if (!lock.tryLock(1, TimeUnit.SECONDS)) {
// if (!lock.tryLock()) {
log.debug("获取立刻失败,返回");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
log.debug("获得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
try {
sleep(2);
// sleep(0.5);
} finally {
lock.unlock();
}
}
(这个tryLock()也可以去解决哲学家就餐问题,尝试获取左手筷子后,再尝试获取右手筷子,这样就能保证都能拿到锁。)
公平锁
ReentrantLock默认是不公平的。
一般不设置公平锁,因为会降低并发度。
下面的例子,如果是公平锁,只能在最后才
public static void isFair() {
lock.lock();
for (int i = 0; i < 500; i++) {
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " running...");
} finally {
lock.unlock();
}
}, "t" + i).start();
}
// 1s 之后去争抢锁,抢得到,说明它是非公平的
sleep(1);
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " start...");
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " running...");
} finally {
lock.unlock();
}
}, "强行插入").start();
lock.unlock();
}
条件变量
synchronized中也有条件变量,当不满足条件时,会进入WaitSet等待。
但是它仅有一个,而ReentrantLock条件变量更强大,支持多个!
基本使用
// 条件变量,可以定义多个
Condition cond1 = lock.newCondition();
Condition cond2 = lock.newCondition();
// 要先获得锁
lock.lock();
// 进入“休息室”等待
cond1.await();
// 叫醒cond1“休息中”的其中一个线程
cond1.signal();
// 叫醒cond1“休息中”的所有线程
cond1.signalAll();
- 使用前要获得锁
- 执行await后会释放锁,进入confitionObject等待
- await的线程被唤醒、打断、超时后,需要重新竞争锁
- 竞争成功后,才继续执行await后面的内容。
实践
感觉。。与Object的wati和notify差不多啊
static ReentrantLock lock = new ReentrantLock();
static Condition waitCigaretteQueue = lock.newCondition();
static Condition waitBreakfastQueue = lock.newCondition();
static volatile boolean hasCigarette = false;
static volatile boolean hasBreakfast = false;
private static void bestTry() {
new Thread(() -> {
try {
lock.lock();
// 与Object的wait相同,会不会也有虚假唤醒呢?
while (!hasCigarette) {
try {
waitCigaretteQueue.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("等到了它的烟");
} finally {
lock.unlock();
}
}).start();
new Thread(() -> {
try {
lock.lock();
while (!hasBreakfast) {
try {
waitBreakfastQueue.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("等到了它的早餐");
} finally {
lock.unlock();
}
}).start();
sleep(1);
sendBreakfast();
sleep(1);
sendCigarette();
}
private static void sendCigarette() {
lock.lock();
try {
log.debug("送烟来了");
hasCigarette = true;
waitCigaretteQueue.signal();
} finally {
lock.unlock();
}
}
private static void sendBreakfast() {
lock.lock();
try {
log.debug("送早餐来了");
hasBreakfast = true;
waitBreakfastQueue.signal();
} finally {
lock.unlock();
}
}