简述
ReentrantLock 是一个可重入的互斥(/独占)锁,又称为“独占锁”。
ReentrantLock通过自定义队列同步器(AQS-AbstractQueuedSychronized,是实现锁的关键)来实现锁的获取与释放。
其可以完全替代 synchronized 关键字。JDK 5.0 早期版本,其性能远好于 synchronized,但 JDK 6.0 开始,JDK 对 synchronized 做了大量的优化,使得两者差距并不大。
“独占”,就是在同一时刻只能有一个线程获取到锁,而其它获取锁的线程只能处于同步队列中等待,只有获取锁的线程释放了锁,后继的线程才能够获取锁。
“可重入”,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁。
该锁还支持获取锁时的公平和非公平性选择。“公平”是指“不同的线程获取锁的机制是公平的”,而“不公平”是指“不同的线程获取锁的机制是非公平的”。
简单实例
import java.util.concurrent.locks.ReentrantLock; /** * Created by zhengbinMac on 2017/3/2. */ public class ReenterLock implements Runnable{ public static ReentrantLock lock = new ReentrantLock(); public static int i = 0; public void run() { for (int j = 0;j<100000;j++) { lock.lock(); // lock.lock(); try { i++; }finally { lock.unlock(); // lock.unlock(); } } } public static void main(String[] args) throws InterruptedException { ReenterLock reenterLock = new ReenterLock(); Thread t1 = new Thread(reenterLock); Thread t2 = new Thread(reenterLock); t1.start();t2.start(); t1.join();t2.join(); System.out.println(i); } }
与 synchronized 相比,重入锁有着显示的操作过程,何时加锁,何时释放,都在程序员的控制中。
为什么称作是“重入”?这是因为这种锁是可以反复进入的。将上面代码中注释部分去除注释,也就是连续两次获得同一把锁,两次释放同一把锁,这是允许的。
注意,获得锁次数与释放锁次数要相同,如果释放锁次数多了,会抛出 java.lang.IllegalMonitorStateException 异常;如果释放次数少了,相当于线程还持有这个锁,其他线程就无法进入临界区。
引出第一个问题:为什么 ReentrantLock 锁能够支持一个线程对资源的重复加锁?
除了简单的加锁、解锁操作,重入锁还提供了一些更高级的功能,下面结合实例进行简单介绍:
中断响应(lockInterruptibly)
对于 synchronized 来说,如果一个线程在等待锁,那么结果只有两种情况,获得这把锁继续执行,或者线程就保持等待。
而使用重入锁,提供了另一种可能,这就是线程可以被中断。也就是在等待锁的过程中,程序可以根据需要取消对锁的需求。
下面的例子中,产生了死锁,但得益于锁中断,最终解决了这个死锁:
1 import java.util.concurrent.locks.ReentrantLock; 2 /** 3 * Created by zhengbinMac on 2017/3/2. 4 */ 5 public class IntLock implements Runnable{ 6 public static ReentrantLock lock1 = new ReentrantLock(); 7 public static ReentrantLock lock2 = new ReentrantLock(); 8 int lock; 9 /** 10 * 控制加锁顺序,产生死锁 11 */ 12 public IntLock(int lock) { 13 this.lock = lock; 14 } 15 public void run() { 16 try { 17 if (lock == 1) { 18 lock1.lockInterruptibly(); // 如果当前线程未被 中断,则获取锁。 19 try { 20 Thread.sleep(500); 21 } catch (InterruptedException e) { 22 e.printStackTrace(); 23 } 24 lock2.lockInterruptibly(); 25 System.out.println(Thread.currentThread().getName()+",执行完毕!"); 26 } else { 27 lock2.lockInterruptibly(); 28 try { 29 Thread.sleep(500); 30 } catch (InterruptedException e) { 31 e.printStackTrace(); 32 } 33 lock1.lockInterruptibly(); 34 System.out.println(Thread.currentThread().getName()+",执行完毕!"); 35 } 36 } catch (InterruptedException e) { 37 e.printStackTrace(); 38 } finally { 39 // 查询当前线程是否保持此锁。 40 if (lock1.isHeldByCurrentThread()) { 41 lock1.unlock(); 42 } 43 if (lock2.isHeldByCurrentThread()) { 44 lock2.unlock(); 45 } 46 System.out.println(Thread.currentThread().getName() + ",退出。"); 47 } 48 } 49 public static void main(String[] args) throws InterruptedException { 50 IntLock intLock1 = new IntLock(1); 51 IntLock intLock2 = new IntLock(2); 52 Thread thread1 = new Thread(intLock1, "线程1"); 53 Thread thread2 = new Thread(intLock2, "线程2"); 54 thread1.start(); 55 thread2.start(); 56 Thread.sleep(1000); 57 thread2.interrupt(); // 中断线程2 58 } 59 }