并发编程[7]_ReentrantLock
ReentrantLock 翻译为可重入锁,是 java.util.concurrent.locks 包中的一个类,可以通过代码调用lock() 和 unlock() 方法来进行加锁解锁。因此一般使用的时候要用到try,finally,在finally中进行锁的释放。
1. ReentrantLock 和 synchronized
ReentrantLock 和 synchronized 这两种同步方式,有相同也有不同之处,可见下表:
synchronized | ReentrantLock |
---|---|
不需手动释放和开启锁,关键字可修饰方法,代码块 | 手动获取和释放锁 |
重量级锁 | 轻量级锁 |
可重入 | 可重入 |
不可设置超时 | 可设置超时 |
不可中断 | 可中断和不可中断都有 |
非公平锁 | 公平锁和非公平锁都有,默认非公平锁 |
单一等待队列 | 多个等待队列,绑定多个Condition类 |
2. ReentrantLock 特性及例子
- 可打断
创建ReentrantLock的实例lock,在主线程对lock加锁,在线程t1等待锁的时候,主线程打断。
使用 lockInterruptibly() 表示是可处理打断的锁。
public class Test1 {
private static final Logger log = LoggerFactory.getLogger(Test1.class);
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
log.debug("准备获取锁");
lock.lockInterruptibly();
} catch (InterruptedException e) {
log.debug("被打断,没有获取到锁");
return;
}
try {
log.debug("获取到了锁");
} finally {
lock.unlock();
log.debug("释放锁");
}
}, "t1");
lock.lock();
try {
t1.start();
Thread.sleep(1000);
t1.interrupt(); // 打断
}finally {
lock.unlock();
}
}
}
运行结果:
2021-04-28 22:39:26.745 [t1] - 准备获取锁
2021-04-28 22:39:27.745 [t1] - 被打断,没有获取到锁
- 可超时
创建ReentrantLock的实例lock,在主线程对lock加锁,在线程t1等待锁的时候,超时返回false退出。
tryLock() 有返回值,返回true,表示获取到了锁。返回false,表示没有获取到锁,拿不到锁不会一直等着,会返回false。
tryLock(long timeout, TimeUnit unit) 方法,可进行超时时间的设置。
public class Test1 {
private static final Logger log = LoggerFactory.getLogger(Test1.class);
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
log.debug("准备获取锁");
if(!lock.tryLock(3, TimeUnit.SECONDS)){
log.debug("超时了,没有获得锁。");
return;
}
} catch (InterruptedException e) {
log.debug("被打断,没有获取到锁");
return;
}
try {
log.debug("获取到了锁");
} finally {
lock.unlock();
log.debug("释放锁");
}
}, "t1");
lock.lock();
try {
t1.start();
Thread.sleep(5000);
}finally {
lock.unlock();
}
}
}
运行结果:
2021-04-28 22:47:26.838 [t1] - 准备获取锁
2021-04-28 22:47:29.842 [t1] - 超时了,没有获得锁。
- 多个等待队列
在使用synchronized时,唤醒线程不能根据条件来进行唤醒,只能唤醒全部线程notifyAll或者随机唤醒notify,所以,当需要不同条件来唤醒时,ReentrantLock提供了newCondition方法,来创建条件对象。
和wait(),notify()类似的方法有Condition中的await() 和 signal(),当然使用前也要获取对应的锁。
下面的例子,是先利用lock实例创建两个condition实例,然后在每个线程中,调用不同condition的await()方法。唤醒的时候,也是调用Condition中的signal()或者signalAll()方法,来唤醒线程。
public class Test1 {
private static final Logger log = LoggerFactory.getLogger(Test1.class);
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
// lock的两个等待队列,可以分别往不同的条件对象加入线程。
// 每个condition绑定不同线程,唤醒的时候就可以分开唤醒了。
Condition animalCondition = lock.newCondition();
Condition plantCondition = lock.newCondition();
Thread t1 = new Thread(() -> {
lock.lock();
try {
try {
log.debug("cat waiting...");
// 和wait()方法类似,也可加时间参数
animalCondition.await();
log.debug("cat awakened");
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}, "cat");
Thread t2 = new Thread(() -> {
lock.lock();
try {
try {
log.debug("rose waiting");
plantCondition.await();
log.debug("rose awakened");
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}, "rose");
t1.start();
t2.start();
// 唤醒cat
Thread.sleep(3000);
lock.lock();
try {
animalCondition.signal();
}finally {
lock.unlock();
}
// 唤醒rose
Thread.sleep(3000);
lock.lock();
try {
plantCondition.signal();
}finally {
lock.unlock();
}
}
}
运行结果:
2021-04-28 23:27:59.269 [cat] - cat waiting...
2021-04-28 23:27:59.269 [rose] - rose waiting
2021-04-28 23:28:02.269 [cat] - cat awakened
2021-04-28 23:28:05.269 [rose] - rose awakened
从打印的时间可以看出唤醒的先后顺序不同。