背景
最近在重构证书管理的需求。之前的代码中,更新证书与吊销证书异步进行,没有进行同步控制。但是会导致主要的两个问题:
1. 更新证书线程和吊销证书线程会对相同的文件进行操作。
2. 不对这两个线程进行同步,日志内容交叉,不好查看。
所以我使用ReentrantLock结合Condition来保证更新证书线程与吊销证书线程交替执行。
synchronized & Object.wait() Object.notify()
ReentrantLock是synchronized的高级版本,而Object.wait()和Object.signal()又与condition.wait()和condition.signal()对应,所以我们先看一看synchronized,再进一步对比ReentrantLock。
synchronized
synchronized锁定的是一块内存区域,而这把锁是一个对象。
synchronized有四种用法:
- 修饰代码块(参数为this)
- 修饰方法
- 修饰代码块(参数为类对象)
- 修饰静态方法
前面两种用法使用的锁是对象锁,而后两种锁使用的类锁。
即在需要执行被synchronize修饰的代码块(参数为this)和成员方法时,需要先获取当前的对象锁。如果两个线程同时都调用了同一个对象的一个被synchronize修饰的方法或方法里有synchronize修饰的代码时,这两个线程无法同时执行。
而在需要执行被synchronize修饰的代码块(参数为类对象)或者静态方法时,需要先获取当前的类对象。如果两个线程同时调用了同一个synchronize修饰的代码块(参数为类对象)或者静态方法时,这两个线程将无法同时执行。
Object.wait() Object.signal()
执行Object.wait()会使当前线程进入阻塞状态,并且释放占用的锁。
执行Object.signal()会唤醒一个阻塞线程,使其进入就绪状态。此时被唤醒的线程会试图去获取锁。
实现线程交替执行的Java代码
public class Main {
public static void main(String[] args) {
Main main = new Main();
new Thread(() -> {
for (int i = 0; i < 3; i++) {
main.method();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 3; i++) {
main.method();
}
}, "B").start();
}
private synchronized void method() {
notify();
System.out.println(Thread.currentThread().getName());
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出如下
A
B
A
B
A
B
ReentrantLock & condition.wait() condition.signal()
ReentrantLock
ReentrantLock提供了比synchronized更高级的功能,下面我们对比一下使用上这两种锁的异同点。
相同的部分:
-
无论是ReentrantLock还是synchronized都是可以重入的。可重入的是指可以申请同一个锁,可以处理这样一种情况:当线程需要递归执行时,重新进入方法还可以申请锁。
你可能会觉得递归进入同一个方法时,已经拥有了锁,为什么还要申请锁? 但是方法执行完之后会释放锁,那么如果我们不再重新申请锁,在内层递归执行完之后, 锁就释放了。回到外层方法时,此时没有锁保护该方法被排他性的访问。
-
都保证了可见性和互斥性。
其中可见性是指线程可以看到另一个线程的修改。那么我们会有一个疑问,保证了哪一部分变量的可见性呢?保证的是被两个线程使用到的共享变量,比如说两个线程都要使用到同一个对象的成员变量,那么一个线程的成员变量就会被两一个对象察觉。为什么会存在可见性的问题? 原因是因为现在的CPU通常是多核,我们可以理解一个核会有一个线程跑在上面,而每一个核又有自己的缓存。这些缓存是 主内存的拷贝,线程在执行时,如果没有同步会直接修改自己缓存中的东西而不会修改主内存。
而互斥性则是指同一个内存区需被使用同一把锁的线程互斥地访问。
不同点:
- 首先synchronized是JVM级别的实现,而ReentrantLock是API级别的;
- ReentrantLock可以用于实现公平锁。
只需在创建ReentrantLock时传入参数true即可,如ReentrantLock lock = new ReentrantLock(true); - ReentrantLock可以与多个Condition绑定。
使用多个Condition可以唤醒特定的线程,比如有两个线程,我们分别定义两个Condition——firstCondition,secondCondition,此时其中一个线程我们调用firstCondition.await()。那么在我们要唤醒该线程时,我们可以调用firstCondition.signal()。
condition.await() condition.signal()
Condition提供了Object的await()和notify()的功能。如下是Java doc中关于Condition的说明。我们可以得到这样的信息:Condition是为了使同一个对象产生多阻塞集的效果。使用synchronized方式同步,我们无法再锁定同一个区域的情况下,调用Object.wait来唤醒指定的线程。
Condition factors out the Object monitor methods (wait, notify and notifyAll) into distinct objects to give the effect of having multiple wait-sets per object, by combining them with the use of arbitrary Lock implementations. Where a Lock replaces the use of synchronized methods and statements, a Condition replaces the use of the Object monitor methods.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。