本节主要从下述四个方面介绍重入锁。
1.什么是重入锁?
2.为什么要引用重入锁?
3.重入锁是怎么实现的?
4.分析java并发包中ReentrantLock。
什么是重入锁
重入锁,支持重进入的锁,表示该锁能够支持一个线程对它重复加锁,即线程在获得锁之后再次获取该锁时不会被阻塞。
为什么要引用重入锁?
以子类重写父类方法为例:
Mutix是不支持重入的锁。(代码摘抄自《java并发编程的艺术》)
1 import java.util.concurrent.TimeUnit; 2 import java.util.concurrent.locks.*; 3 4 5 6 public class Mutix implements Lock 7 { 8 private static class Sync extends AbstractQueuedSynchronizer{ 9 //是否处于占用状态 10 protected boolean isHeldExclusively(){ 11 return this.getState()==1; 12 } 13 protected boolean tryAcquire(int acquires){ 14 if(compareAndSetState(0,1)){ 15 setExclusiveOwnerThread(Thread.currentThread()); 16 return true; 17 } 18 return false; 19 20 } 21 protected boolean tryRelease(int releases){ 22 if(getState()==0)throw new IllegalMonitorStateException(); 23 setExclusiveOwnerThread(null); 24 setState(0); 25 26 return true; 27 28 } 29 Condition newCondition(){ 30 return new ConditionObject(); 31 } 32 33 } 34 private final Sync sync=new Sync(); 35 36 @Override 37 public void lock() 38 { 39 // TODO Auto-generated method stub 40 sync.acquire(1); 41 } 42 43 @Override 44 public void lockInterruptibly() throws InterruptedException 45 { 46 // TODO Auto-generated method stub 47 sync.acquireInterruptibly(1); 48 } 49 50 @Override 51 public Condition newCondition() 52 { 53 // TODO Auto-generated method stub 54 return sync.newCondition(); 55 } 56 57 @Override 58 public boolean tryLock() 59 { 60 // TODO Auto-generated method stub 61 return sync.tryAcquire(1); 62 } 63 64 @Override 65 public boolean tryLock(long arg0, TimeUnit arg1) 66 throws InterruptedException 67 { 68 // TODO Auto-generated method stub 69 return sync.tryAcquireNanos(1, arg1.toNanos(arg0)); 70 } 71 72 @Override 73 public void unlock() 74 { 75 // TODO Auto-generated method stub 76 sync.release(1); 77 } 78 public boolean hasQueuedThreads(){ 79 return sync.hasQueuedThreads(); 80 } 81 82 }
View Code
分别以支持重入和不支持重入进行测试;
测试代码如下:
1 import java.util.concurrent.locks.ReentrantLock; 2 3 4 public class TestLock 5 { 6 static Mutix lock=new Mutix(); 7 // static ReentrantLock lock=new ReentrantLock(); 8 9 public static class Widget { 10 public void doSomething() { 11 lock.lock(); 12 try{ 13 System.out.println("父类Widget,线程名称:" +Thread.currentThread().getName()); 14 }finally{ 15 lock.unlock(); 16 } 17 } 18 } 19 20 public static class LoggingWidget extends Widget { 21 public void doSomething() { 22 lock.lock(); 23 try{ 24 System.out.println("子类LoggingWidget: calling doSomething,线程名称:" +Thread.currentThread().getName()); 25 super.doSomething(); 26 }finally{ 27 lock.unlock(); 28 } 29 } 30 } 31 32 33 34 /** 35 * @param args 36 */ 37 public static void main(String[] args) 38 { 39 LoggingWidget test=new LoggingWidget(); 40 test.doSomething(); 41 } 42 43 }
View Code
当使用ReentrantLock可重入锁时,显示结果如下:
子类LoggingWidget: calling doSomething,线程名称:main
父类Widget,线程名称:main
当使用Mutix不可重入锁时,结果如下:
子类LoggingWidget: calling doSomething,线程名称:main
此时程序并没有结束,而是一直在等待父类的锁,通过jstack命令,查看到Main进程处于等待状态。按正常的处理逻辑,子类是允许调用父类的方法,故重入锁在实际应用中还是很重要的。
重入锁是怎么实现?
1)线程再次获取锁。需要判断当前获取锁的线程是否为当前占据锁的线程,如果是,则再次获取成功。
2)锁的最终释放。若线程重复了N次获得了锁,那需要释放N次才能真正释放该锁。线程再次获取锁时,计时器加1,当释放锁时,计数器减1。
分析java并发包中ReentrantLock。
1)自定义组合同步器实现锁的获取和释放,默认是以非公平形式获取锁。
1 final boolean nonfairTryAcquire(int acquires) { 2 final Thread current = Thread.currentThread();//当前线程 3 int c = getState();//获取同步状态 4 if (c == 0) {//如果同步状态为0,表示当前没有线程获取锁 5 if (compareAndSetState(0, acquires)) {//通过CAS算法获取锁 6 setExclusiveOwnerThread(current); 7 return true; 8 } 9 } 10 else if (current == getExclusiveOwnerThread()) { 11 //如果同步状态不为0,表示已经有线程获取了该锁,判断获取锁的线程是否为当前线程,如果时,同步状态加acquires,一般为加1 12 int nextc = c + acquires; 13 if (nextc < 0) // overflow 14 throw new Error("Maximum lock count exceeded"); 15 setState(nextc);//设置同步状态 16 return true; 17 } 18 return false; 19 } 20 21 protected final boolean tryRelease(int releases) { 22 int c = getState() - releases;//计算当前同步状态减去releases 23 if (Thread.currentThread() != getExclusiveOwnerThread())//如果当前线程不等于获取锁的线程 24 throw new IllegalMonitorStateException(); 25 boolean free = false; 26 if (c == 0) {//如果C==0,表示同步状态完全释放,将占用线程设置为Null,并且返回true 27 free = true; 28 setExclusiveOwnerThread(null); 29 } 30 setState(c); 31 return free; 32 }
View Code
在调用ReentrantLock的lock和unlock方法时,实际是调用同步器中的方法。
1 public boolean tryLock() { 2 return sync.nonfairTryAcquire(1); 3 } 4 5 public boolean tryLock(long timeout, TimeUnit unit) 6 throws InterruptedException { 7 return sync.tryAcquireNanos(1, unit.toNanos(timeout)); 8 } 9 public void unlock() { 10 sync.release(1); 11 }
View Code
从代码中可以看到,最终调用的是sync中的方法,对于用户而言,sync是透明的。
2)公平和非公平获取锁。
1 /** 2 * Sync object for non-fair locks 3 */ 4 static final class NonfairSync extends Sync { 5 private static final long serialVersionUID = 7316153563782823691L; 6 7 /** 8 * Performs lock. Try immediate barge, backing up to normal 9 * acquire on failure. 10 */ 11 final void lock() { 12 if (compareAndSetState(0, 1)) 13 setExclusiveOwnerThread(Thread.currentThread()); 14 else 15 acquire(1); 16 } 17 18 protected final boolean tryAcquire(int acquires) { 19 return nonfairTryAcquire(acquires); 20 //直接调用Sync的方法。 21 } 22 }
View Code
1 static final class FairSync extends Sync { 2 private static final long serialVersionUID = -3000897897090466540L; 3 4 final void lock() { 5 acquire(1); 6 } 7 8 /** 9 * Fair version of tryAcquire. Don't grant access unless 10 * recursive call or no waiters or is first. 11 */ 12 protected final boolean tryAcquire(int acquires) { 13 final Thread current = Thread.currentThread(); 14 int c = getState(); 15 if (c == 0) { 16 if (!hasQueuedPredecessors() && 17 compareAndSetState(0, acquires)) { 18 setExclusiveOwnerThread(current); 19 return true; 20 } 21 } 22 else if (current == getExclusiveOwnerThread()) { 23 int nextc = c + acquires; 24 if (nextc < 0) 25 throw new Error("Maximum lock count exceeded"); 26 setState(nextc); 27 return true; 28 } 29 return false; 30 } 31 }
View Code
公平和非公平最大区别在于:公平锁需要判断当前节点的前一个节点是否为头节点(hasQueuedPredecessors() ),如果是,才允许通过CAS算法获取锁,如果不是,表示有线程比当前线程更早的获取锁,因此需要等待前驱线程获取锁并释放锁之后才能继续获取锁。非公平不需要进行判断。公平锁保证了线程处理之间的公平,通过FIFO,保证了公平性,而代价是进行大量的线程切换。非公平锁虽然有时会造成线程饥饿,但极少的线程切换,保证了更大的吞吐量。
ReentrantLock锁默认采用的是非公平锁。
1 public ReentrantLock() { 2 sync = new NonfairSync();//默认采用非公平锁 3 }
View Code
总结:最近这几天在看《JAVA并发编程的艺术》,作者讲解的很详细,本来不想写博客,网上关于这些的文章已经非常多了,而且该书讲解的那么透彻,但后来想想,在写博客其实也是整理、梳理知识的过程,如果自己都没有弄明白,如何能够写出来。