您的位置:首页 > 编程语言 > Java开发

Java并发编程之ReenTrantLock

2018-01-11 14:55 471 查看




如果锁具备可重入性,则称作为可重入锁。像
synchronized
ReentrantLock
都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个
synchronized
方法时,比如说
method1
,而在
method1
中会调用另外一个
synchronized
方法method2,此时线程不必重新去申请锁,而是可以直接执行方法
method2




可中断锁:顾名思义,就是可以相应中断的锁。在Java中,
synchronized
就不是可中断锁,而
Lock
是可中断锁。如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。在前面演示
lockInterruptibly()
的用法时已经体现了
Lock
的可中断性。



公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。在Java中,
synchronized
就是非公平锁,它无法保证等待的线程获取锁的顺序。而对于
ReentrantLock
ReentrantReadWriteLock
,它默认情况下是非公平锁,但是可以设置为公平锁。我们可以在创建
ReentrantLock
对象时,通过以下方式来设置锁的公平性:
ReentrantLock
lock = new ReentrantLock(true);
如果参数为
true
表示为公平锁,为
fasle
为非公平锁。默认情况下,如果使用无参构造器,则是非公平锁。 



读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。
ReadWriteLock
就是读写锁,它是一个接口,
ReentrantReadWriteLock
实现了这个接口。可以通过
readLock()
获取读锁,通过
writeLock()
获取写锁。

/**
* 默认构造方法,非公平锁
*/
public ReentrantLock() {
sync = new NonfairSync();
}
 
/**
* true公平锁,false非公平锁
* @param fair
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}

以下是一个简单的使用实例

public class ThreadTest {
public static void main(String[] args) {
Counter counter = new Counter();
 
for (int i = 0; i < 5; i++) {
new Thread(() -> counter.run()).start();
}
}
}
 
class Counter {
private final Lock lock = new ReentrantLock();
 
public void run() {
try {
lock.lock();
for(int i = 0 ; i < 3 ; i++){
System.out.println(Thread.currentThread().getName() + "run......");
TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000));
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally{
lock.unlock();
}
}
}





从名字上理解,
ReenTrantLock
的字面意思就是再进入的锁,其实
synchronized
关键字所使用的锁也是可重入的,两者关于这个的区别不大。两者都是同一个线程没进入一次,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。



synchronized
是依赖于
JVM
实现的,而
ReenTrantLock
JDK
实现的,有什么区别,说白了就类似于操作系统来控制实现和用户自己敲代码实现的区别。前者的实现是比较难见到的,后者有直接的源码可供阅读。 

synchronized
JVM
层面实现,不但可以通过一些监控工具监控锁定,而且在代码执行出现异常,
JVM
自动释放锁定; 
Lock
是通过代码实现,为了保证锁定一定会被释放,一般会将
unLock()
放到
flyinal{}
中。



在资源竞争不激烈的情况下,
synchronized
的性能要优于
ReentrantLock
,但在资源竞争很激烈的情况下,
synchronized
的性能会下降几十倍,但是
ReentrantLock
的性能能维持常态。

Synchronized
优化以前,
synchronized
的性能是比
ReenTrantLock
差很多的,但是自从
Synchronized
引入了偏向锁,轻量级锁(自旋锁)后,两者的性能就差不多了,在两种方法都可用的情况下,官方甚至建议使用
synchronized
,其实
synchronized
的优化我感觉就借鉴了
ReenTrantLock
中的
CAS
技术。都是试图在用户态就把加锁问题解决,避免进入内核态的线程阻塞。



便利性:很明显
Synchronized
的使用比较方便简洁,并且由编译器去保证锁的加锁和释放,而
ReenTrantLock
需要手工声明来加锁和释放锁,为了避免忘记手工释放锁造成死锁,所以最好在
finally
中声明释放锁。

锁的细粒度和灵活度:很明显
ReenTrantLock
优于
Synchronized






ReenTrantLock
可以指定是公平锁还是非公平锁。而
synchronized
只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。



ReenTrantLock
提供了一个
Condition
(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像
synchronized
要么随机唤醒一个线程,要么唤醒全部线程。



tryLock(long
timeout, TimeUnit unit)
,如果获取了锁定立即返回
true
,如果别的线程正持有锁,将等待参数给定的时间,在等待的过程中,如果获取了锁定,返回
true
,如果等待超时,返回
false
,所以
lock()
方法相当
trylock
传递个无限大的时间参数;



ReenTrantLock
提供了一种能够中断等待锁的线程的机制,通过
lock.lockInterruptibly()
来实现这个机制。 
lockInteruptibly
,如果获取了锁定立即返回,反之,当前线程处理休眠,直至获取锁,或者当前线程线程被其他线程中断。



tryLock()
,如果获取了锁立即返回
true
,如果别的线程下持有,立即返回
false
;

使用
synchronized
时,如果A不释放,B将一直等待下去,无法中断。 

使用
ReentrantLock
时,如果A不释放,B可以在等待足够长时间后,停止等待,继续执行其他事务。

一般情况下,只有在我们需要实现特定的功能时,会使用
ReentrantLock
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: