ReentrantLock与synchronized的比较
2020-05-01 本文已影响0人
文景大大
一、ReentrantLock与synchronized的区别
首先构造一个线程不安全的例子:
@Slf4j
public class Counter {
private int count = 0;
public int getCount(){
return count;
}
public void plus(){
for (int i = 0; i < 3; i++) {
log.info("{}的count为:{}", Thread.currentThread().getName(),count++);
}
}
}
@Slf4j
public class MyThread implements Runnable {
private Counter counter;
public MyThread(Counter counter){
this.counter = counter;
}
@Override
public void run() {
counter.plus();
log.info("{}执行完毕counter为:{}", Thread.currentThread().getName(), counter.getCount());
}
}
@Slf4j
public class Test001 {
public static void main(String[] args) {
Counter counter = new Counter();
Thread thread1 = new Thread(new MyThread(counter));
thread1.start();
Thread thread2 = new Thread(new MyThread(counter));
thread2.start();
}
}
执行结果的一种可能是:
Thread-0的count为:0
Thread-1的count为:1
Thread-1的count为:3
Thread-0的count为:2
Thread-1的count为:4
Thread-0的count为:5
Thread-0执行完毕counter为:6
Thread-1执行完毕counter为:6
不光出现了两个线程交替执行的情况,而且两个线程出现了脏读。
为了解决线程的并发安全问题,我们可以使用synchronized来解决:
public void plus(){
synchronized (this) {
for (int i = 0; i < 3; i++) {
log.info("{}的count为:{}", Thread.currentThread().getName(),count++);
}
}
}
同时, 也可以使用今天的主角ReentrantLock来解决:
@Slf4j
public class Counter {
private Lock lock = new ReentrantLock();
private int count = 0;
public int getCount(){
return count;
}
public void plus(){
lock.lock();
for (int i = 0; i < 3; i++) {
log.info("{}的count为:{}", Thread.currentThread().getName(),count++);
}
lock.unlock();
}
}
在lock的日常的使用中,Java编码规范建议我们:
1. 上锁操作后面必须紧跟try代码块,且unlock要放在finally的第一行;
2. 在使用阻塞等待获取锁的方式中,必须在try代码块之外,并且在加锁方法与try之间没有任何可能会抛出异常的方法调用,从而避免在加锁后,无法执行finally中的释放锁操作。
所以,如山使用lock的例子应该改为:
public void plus() {
lock.lock();
// lock加锁后紧跟try
try {
for (int i = 0; i < 3; i++) {
log.info("{}的count为:{}", Thread.currentThread().getName(),count++);
}
} catch (Exception e) {
log.info("{}",e);
} finally {
// 始终在finally中释放锁
lock.unlock();
}
}
记录一下ReentrantLock和synchronized的区别:
- synchronized是Java语言的关键字,其加锁与解锁由JVM实现,比较简洁;而ReentrantLock是API,需要开发者自己配合try-catch-finally来手动实现加锁和解锁,比较繁琐,且容易忘记;
- synchronized等待无期限设置,而ReentrantLock可以通过
tryLock(),tryLock(Long,TimeUnit)
的方式尝试获取锁,如果获取不到可以执行其它逻辑; - synchronized等待不可被外部中断;而ReentrantLock可以通过
lock.lockInterruptibly();
的方式实现可被外部中断等待; - synchronized是非公平锁;而ReentrantLock可以通过设置
new ReentrantLock(true);
实现公平锁;
三、Condition与wait/notify的区别
在上一篇文章中,我们讲过使用Object的wait和notify/notifyAll可以实现线程间的通信,其实使用condition也同样可以实现。
@Slf4j
public class Counter {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void plus1() {
lock.lock();
try {
log.info("线程:{}开始wait:{}",Thread.currentThread().getName(),System.currentTimeMillis());
// 当前线程释放lock锁资源,并在此等待
condition.await();
log.info("线程:{}结束wait:{}",Thread.currentThread().getName(),System.currentTimeMillis());
} catch (Exception e) {
log.info("{}",e);
} finally {
lock.unlock();
}
}
public void plus2() {
lock.lock();
try {
log.info("线程:{}开始wait:{}",Thread.currentThread().getName(),System.currentTimeMillis());
// 当前线程释放lock锁资源,并在此等待
condition.await();
log.info("线程:{}结束wait:{}",Thread.currentThread().getName(),System.currentTimeMillis());
} catch (Exception e) {
log.info("{}",e);
} finally {
lock.unlock();
}
}
public void minus(){
lock.lock();
try {
log.info("线程:{}开始signal:{}",Thread.currentThread().getName(),System.currentTimeMillis());
// 唤醒相同condition等待队列中的一个线程
condition.signal();
// 唤醒相同condition等待队列中的所有await线程
//condition.signalAll();
log.info("线程:{}结束signal:{}",Thread.currentThread().getName(),System.currentTimeMillis());
} catch (Exception e) {
log.info("{}",e);
} finally {
lock.unlock();
}
}
}
除了可以实现Object的wait和notify/notifyAll的功能之外,Condition还可以唤醒指定的线程,只需要使用不同的condition即可。
@Slf4j
public class Counter {
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
public void plus1() {
lock.lock();
try {
log.info("线程:{}开始wait:{}",Thread.currentThread().getName(),System.currentTimeMillis());
condition1.await();
log.info("线程:{}结束wait:{}",Thread.currentThread().getName(),System.currentTimeMillis());
} catch (Exception e) {
log.info("{}",e);
} finally {
lock.unlock();
}
}
public void plus2() {
lock.lock();
try {
log.info("线程:{}开始wait:{}",Thread.currentThread().getName(),System.currentTimeMillis());
condition2.await();
log.info("线程:{}结束wait:{}",Thread.currentThread().getName(),System.currentTimeMillis());
} catch (Exception e) {
log.info("{}",e);
} finally {
lock.unlock();
}
}
public void minus(){
lock.lock();
try {
log.info("线程:{}开始condition1的signal:{}",Thread.currentThread().getName(),System.currentTimeMillis());
condition1.signal();
log.info("线程:{}结束condition1的signal:{}",Thread.currentThread().getName(),System.currentTimeMillis());
Thread.sleep(2000);
log.info("线程:{}开始condition2的signal:{}",Thread.currentThread().getName(),System.currentTimeMillis());
condition2.signal();
log.info("线程:{}结束condition2的signal:{}",Thread.currentThread().getName(),System.currentTimeMillis());
} catch (Exception e) {
log.info("{}",e);
} finally {
lock.unlock();
}
}
}
记录下condition与wait/notify的区别:
- condition需要配合ReentrantLock使用;wait/notify需要配合synchronized使用;
- condition能够支持唤醒指定的线程;wait/notify不行,只能随机唤醒;
- condition可以通过
await(Long,TimeUnit)
来有限等待锁资源;wait/notify不行,必须无限期等待;