Java中Synchronized与ReentrantLock的不同以及ReentrantLock的使用
首先,ReentrantLock是在Java1.5中被加入的,所以在之前的Java版本中是不存在 ReentrantLock的。ReentrantLock是Java并发包中非常有用的一个类,比如在ConcurrentHashMap中就用到了ReentrantLock。ReentrantLock类有两个重要的特征,其中一个是当我们尝试获取锁时,我们能够中断这个获取锁的行为;另一个是我们能够指定一个获取锁的等到时间。这两个特征对于构建高响应性以及可伸缩性的系统非常重要。简单的说,ReentrantLock继承了synchronized关键字的功能,但是又提供了更多控制锁的行为。以下源码均来自Java8
目录:
[toc]
什么是ReentrantLock
查看ReentrantLock的源代码,我们可以看到ReentrantLock实现了Lock接口以及java.io.Serializable接口。ReentrantLock是一种互斥锁,类似于Java关键字synchronized提供的隐式锁。但是,ReentrantLock提供了 一些扩展功能如公平性,这个功能能够用于为最长等待的线程提供锁定。获取锁是通过调用lock方法,直到线程调用了unlock方法才释放锁。当我们创建ReentrantLock时,可以提供一个公平参数。
ReentrantLock类似于隐式锁定提供了相同的可见性和顺序保证,也就是unlock方法一定发生在另外一个线程调用lock方法之前。
隐式锁:隐式获取锁,synchronized是它的代表,使用者不需要关心内部锁的获取和释放,锁的相关操作都由具体的关键字完成
显示锁:显示的获取锁,Lock是其代表,需要使用者在使用的时候显示地获取和释放锁
ReentrantLock和synchronized的区别
尽管ReentrantLock像隐式锁一定提供了相同的可见性和顺序保证,但是ReentrantLock提供了更多的功能并且在一些方面跟synchronized是不同的。就如前面所说,ReentrantLock与synchronized主要的区别是在获取锁的中断操作能力以及等待获取锁的时间上。
1. 公平性(fairness)。synchronized不支持公平性。一旦锁被释放了,任何线程都能获得锁,不能指定哪个线程能够优先获取锁;另一方面,通过在创建ReentrantLock实例的时候我们可以指定公平属性。在竞争锁的过程中,通过指定公平性,我们能够为最长等待线程(即等待此锁最长的线程)提供锁。
ReentrantLock有两个构造函数,如下代码,可见无参数的构造函数是非公平性的。
2. 第二个主要的区别是tryLock方法。ReentrantLock提供了tryLock方法,当且仅当锁是可用的并且没有被其他的线程持有tryLock方法才能获取此锁。这减轻了线程在等待锁时产生的阻塞。
3. ReentrantLock在等待获取锁的过程中提供了中断,即我们能够中断获取锁的过程。在synchronized中,一个线程在等到锁的过程中会一直阻塞,如果陷入了无限等待中,我们是没有办法控制这个过程的。而ReentrantLock提供了一个lockInterruptibly方法,当线程正在等待获取锁的过程中,我们能够用此方法来中断线程。同时我们也可以给tryLock指定一个超时时间,在这个时间范围内如果没有获得锁,则返回false。
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
- ReentrantLock提供了获取等到锁的所有线程,如getQueuedThreads()方法
ReentrantLock的优势
其实在上一个小节中已经部分说明ReentrantLock 的优势,这里做一个总结
1. 能够中断获取锁过程
2. 在等待锁时可以指定超时时间
3. 能够创建公平锁
4. 获取所有等待锁的线程
5. 可以灵活的尝试获取锁而不锁定线程
ReentrantLock的劣势
凡事都有两面性,说完ReentrantLock的优势,再来说说ReentrantLock的劣势。ReentrantLock的主要缺点是使用ReentrantLock时必须将ReentrantLock中的方法放到try-finally结构中,这就造成了代码不易读并且隐藏了业务逻辑。尽管类似于Eclipse及Netbeans的IDE能够为我们添加try catch块,但是代码仍然显得有点乱。另一个缺点是,使用ReentrantLock之后,类的获取以及释放全部由程序员负责,这虽然为我们编写代码提供了更多功能,但是也带来了新的bug,特别是我们忘记在finally块中释放锁。有点类似于C++中的free与delete一定要配对使用。
Java中使用ReentrantLock及Lock的例子
代码非常简单,就是两个线程分别修改count的值并且输出到标准输入上。getCount用ReentrantLock加锁,而getCountTwo用synchronized关键字加锁。从代码中可以看到采用synchronized关键字的代码更加易读,但是相对于ReentrantLock没有那么灵活。
package com.ll.concurrent;
import java.util.concurrent.locks.ReentrantLock;
/**
* @describe 如何使用ReentrantLock类
* @author liang
* @date 创建时间:2017年6月10日
*
*/
public class ReentrantLockHowto {
//一个非公平的ReentrantLock
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
//在lock上获取锁,在finally中释放锁
public int getCount() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " getCount方法获取count的值:" + count);
return count++;
} finally {
lock.unlock();
}
}
//采用synchronized隐式加锁
public synchronized int getCountTwo() {
System.out.println(Thread.currentThread().getName() + " getCountTwo方法获取count的值: " + count);
return count++;
}
public static void main(String args[]) {
final ReentrantLockHowto counter = new ReentrantLockHowto();
Thread t1 = new Thread() {
public void run() {
while (counter.getCount() < 6) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t2 = new Thread() {
public void run() {
while (counter.getCount() < 6) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.start();
t2.start();
}
}
代码在本人github,其中说明了java中一些类的使用方式,欢迎start。
参考链接
ReentrantLock Example in Java, Difference between synchronized vs ReentrantLock
上一篇: atomic 包(一)
推荐阅读
-
Java多线程——JUC ReentrantLock使用详解一篇就够,以及使用ReentrantLock避免死锁情况的产生
-
Java中Synchronized与ReentrantLock的不同以及ReentrantLock的使用
-
java的两种同步方式, Synchronized与ReentrantLock的区别
-
Java中String与StringBuffer以及StringBuilder的使用区别详解
-
Java中String与StringBuffer以及StringBuilder的使用区别详解
-
Java多线程——JUC ReentrantLock使用详解一篇就够,以及使用ReentrantLock避免死锁情况的产生