ReentrantLock和synchronized都是用来确保线程的安全性,为了在线程并发时保证线程安全,我们必须给需要进行的操作方法加“锁”。
synchronized的实现方式:
使用Synchronized关键字可以将一段代码逻辑,用一把锁给锁起来,只有获得了这把锁的线程才访问。并且同一时刻, 只有一个线程能持有这把锁, 这样就保证了同一时刻只有一个线程能执行被锁住的代码,从而确保代码的线程安全。
synchronized 关键字的用法:
- 修饰实例方法:synchronized修饰实例方法, 则用到的锁,默认为this当前方法调用对象;
- 修饰静态方法:synchronized修饰静态方法, 则其所用的锁,默认为Class对象;
- 修饰代码块:synchronized修饰代码块, 则其所用的锁,是某个指定Java对象;
synchronized修饰实例方法(当前对象当“锁”):
// 递增
public void add() {
for (int i = 0; i < 10000; i++) {
synchronized (this) {
Counter1.count += 1;
}
}
}
// 递减
public synchronized void dec() {
for (int i = 0; i < 10000; i++) {
Counter1.count -= 1;
}
}
synchronized修饰静态方法(当前对象的class对象当“锁”):
// 递增
public void add() {
for (int i = 0; i < 10000; i++) {
synchronized (Counter2.class) {
Counter2.count += 1;
}
}
}
// 递减
public synchronized static void dec() {
for (int i = 0; i < 10000; i++) {
Counter2.count -= 1;
}
}
synchronized修饰代码块(Object对象当“锁”):
//递增线程
class AddThread extends Thread{
private static final Object lock=new Object();
@Override
public void run() {
for(int i=0;i<10000;i++) {
synchronized (lock) {
Counter.count+=1;
}
}
}
}
注意:
当一个线程访问一个对象的synchroized(this)代码块时,另一个线程仍可以访问该对象的非synchroized(this)代码块;
离开synchroized(this)代码块后,该线程所持有“锁”会自动释放。
ReentrantLock的使用:
从Java 5开始,引入了一个高级处理并发java.util.concurrent包,它提供了大量更高级的并发功能,能大大简化多线程程序的编写。
另外,ReentrantLock还有三个内部类:Sync、NonfairSync、FairSync。
- NonfairSync 类继承了 Sync类,表示采用非公平策略获取锁:每一次都尝试获取锁,不会按照公平等待的原则进行等待,不会让等待时间最久的线程获得锁。
- FairSync类也继承了 Sync类,表示采用公平策略获取锁:当资源空闲时,它总是会先判断 sync队列是否有等待时间更长的线程,如果存在,则将当前线程加入到等待队列的尾部,实现了公平获取原则。
- ReentrantLock构造函数:默认是采用的非公平策略获取锁。
使用ReentrantLock代码参考如下:
class Counter {
// 用于计数的公共变量
public static int count = 0;
//创建锁
private final ReentrantLock lock = new ReentrantLock();
// 递增
public void add() {
for (int i = 0; i < 10000; i++) {
lock.lock();
try {
Counter.count += 1;
} finally {
lock.unlock();
}
}
}
// 递减
public void dec() {
for (int i = 0; i < 10000; i++) {
lock.lock();
try {
Counter.count -= 1;
} finally {
lock.unlock();
}
}
}
}
ReentrantLock与synchronized的区别:
ReentrantLock | synchronized | |
锁实现机制 | AQS | 监视器Monitor |
获取锁 | 可以通过tryLock()尝试获取锁,更灵活 | 线程抢占模型 |
释放锁 | 必须显示通过unlock()释放锁 | 自动释放 |
锁类型 | 支持公平锁和非公平锁 | 非公平锁 |
可重入性 | 可重入 | 可重Rett |
ReentrantLock 区别于 synchronized来说,它不仅可以开锁关锁,它还可以尝试锁tryLock(),因此在实际的引用场景中,当产生“死锁”时,ReentrantLock可以进行一些额外处理而不是无限等待下去。所以说ReentrantLock的安全性更好一些。