(1) 创建一个ReentrantReadWriteLock对象
- private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
(2)抽取读锁和写锁:
- private Lock readLock = rwl.readLock();//得到一个可被多个读操作共用的读锁,但它会排斥所有写操作
- private Lock writeLock = rwl.writeLock();//得到一个写锁,它会排斥所有其他的读操作和写操作
(3) 对所有访问者加读锁
- public double getTotalBalance(){
- readLock.lock();
- try{...};
- finally{readLock.unlock();}
- }
对所有修改者加写锁
- public void transfer(){
- writeLock.lock();
- try{...};
- finally{writeLock.unlock();}
- }
1、ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候
线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,
如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断
如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情
ReentrantLock获取锁定与三种方式:
a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
c)tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断
2、synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中
3、在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;
线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,
如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断
如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情
ReentrantLock获取锁定与三种方式:
a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
c)tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断
2、synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中
3、在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;
可重入的锁ReentrantLock:使用ReentrantLock锁最简单的一个例子:
复制内容到剪贴板
上面这段代码,首先创建了一个lock,然后调用它的lock()方法,开启锁定,在最后调用它的unlock()解除锁定。值得注意的时,一般在使用锁时,都应该按上面的风格书写代码,即lock.unlock()最好放在finally块,这样可以防止,执行do something时发生异常后,导致锁永远无法被释放。注意:lock与unlock一定要
配对。
代码:
Lock lock = new ReentrantLock();
try {
lock.lcok();
// do something
} finally {
lock.unlock();
}
到此,还没发现Lock与synchronized有什么不同,Lock与synchronized不同之处主要体现在Lock比synchronized更灵活得多,而这些灵活又主要体现在如下的几个方法
//lock()
tryLock()
tryLock(long timeout, TimeUnit timeUnit)
lockInterruptibly()
//unlock()
A、 trylock()方法 :如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
B、 tryLock(long timeout, TimeUnit timeUnit)方法 :如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
是不是比synchronized灵活就体现出来了,打个不是很恰当的比分:你现在正在忙于工作,突然感觉内急,于是你跑向洗手间,到门口发现一个“清洁中,暂停使用”的牌牌。没办法,工作又忙,所以你只好先放弃去洗手间回去忙工作,可能如此反复,终于你发现可以进了,于是......
像这样的场景用synchronized你怎么实现?没办法,如果synchronized,当你发现洗手间无法暂时无法进入时,就只能乖乖在门口干等了。而使用trylock()呢,首先你试着去洗手间,发现暂时无法进入(trylock返回false),于是你继续忙你的工作,如此反复,直到可以进入洗手间为止(trylock返回true)。甚至,你非常急,你可以尝试性的在门口等20秒,不行再去忙工作(trylock(20, TimeUnit.SECONDS);)。
C、 lockInterruptibly()方法
lockInterruptibly()方法的执行如下:
如果该锁定没有被另一个线程保持,则获取该锁定并立即返回,将锁定的保持计数设置为 1。
如果当前线程已经保持此锁定,则将保持计数加 1,并且该方法立即返回。
如果锁定被另一个线程保持,则出于线程调度目的,禁用当前线程,并且在发生以下两种情况之一以前,该线程将一直处于休眠状态:
a、锁定由当前线程获得;
b、或者其他某个线程中断当前线程。
如果当前线程获得该锁定,则将锁定保持计数设置为1。
如果当前线程:
a、在进入此方法时已经设置了该线程的中断状态;
b、或者在等待获取锁定的同时被中断。
则抛出 InterruptedException,并且清除当前线程的已中断状态。
即lockInterruptibly()方法允许在等待时由其它线程调用它的Thread.interrupt方法来中断等待而直接返回,这时不再获取锁,而会抛出一个InterruptedException。
ReentrantReadWriteLock可以作为公平锁和非公平锁
公平锁:所有等待锁的线程都保存着一个队列中,当获得锁的线程执行完毕释放锁之后,队列中线程按照先进先出的顺序获取锁
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
非公平锁:,所有等待锁的线程都保存着一个队列中,当获得锁的线程执行完毕释放锁之后,JVM会在等待队列中随机挑选一个等待线程获取锁
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(false);
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(false);