重入锁ReentrantLock用法以及如何实现重进入
在java多线程中,可以使用synchronized实现线程之间的同步互斥,但在jdk1.5中增加了ReentrantLock类也能达到同样的效果,而且在使用上更加灵活,扩展功能上更加强大。
创建MyService.java类,代码如下:
package com.lit.reentreantlock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class MyService { private Lock lock = new ReentrantLock() ; public void testMethod(){ lock.lock(); for(int i = 0 ; i < 5 ; i++){ System.out.println("ThreadName = "+Thread.currentThread().getName()+" "+(i+1)); } lock.unlock(); } }
调用ReentrantLock对象的lock()方法获取锁,unlock()方法释放锁。
再创建MyThread.java,代码如下:
package com.lit.reentreantlock; public class MyThread extends Thread{ private MyService service ; public MyThread(MyService service){ this.service = service ; } @Override public void run() { service.testMethod(); } }
运行类Run.java如下:
package com.lit.reentreantlock; public class Run { public static void main(String[] args) { MyService service = new MyService() ; MyThread t1 = new MyThread(service) ; MyThread t2 = new MyThread(service) ; MyThread t3 = new MyThread(service) ; MyThread t4 = new MyThread(service) ; MyThread t5 = new MyThread(service) ; t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); } }
程序运行结果如下:
ThreadName = Thread-1 1 ThreadName = Thread-1 2 ThreadName = Thread-0 1 ThreadName = Thread-0 2 ThreadName = Thread-3 1 ThreadName = Thread-3 2 ThreadName = Thread-2 1 ThreadName = Thread-2 2 ThreadName = Thread-4 1 ThreadName = Thread-4 2
从线程的运行结果来看,线程之间的打印是分组打印的,打印完毕将锁释放其他线程才可以继续打印。
以上简单的介绍了ReentrantLock的用法,ReentrantLock是重入锁,顾名思义,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁。实现重进入需要解决一下两个问题:
(1)线程再次获取锁。 锁需要去识别获取锁的线程是否是当前占据锁的线程,如果是,则再次成功获取锁。
(2)锁的最终释放。线程重复n次获取了锁,随后在第n次获取锁之后对锁进行进行释放,其他线程之后能够获取到该锁。那么锁的最终释放要求锁在获取时候对于每一次成功获取进行自增计数,计数表示被重复获取的次数,而锁被释放时,计数自减,当计数减为0时表示锁获取成功。
ReentrantLock的实现是依靠队列同步器AbstractQueuedSynchronizer实现的,ReentrantLock内部定义了一个静态内部类,也就是AbstractQueuedSynchronizer的子类Sync,Sync自定义实现了AbstractQueuedSynchronizer的模板方法。以非公平锁为例,ReentrantLock在获取锁定的时候通过调用的代码清单如下。
final boolean nonfairTryAcquire(int acquires) { //获取当前线程 final Thread current = Thread.currentThread(); //获取同步状态 int c = getState(); //如果锁没有被占用 if (c == 0) { //CAS设置同步状态 if (compareAndSetState(0, acquires)) { //设置独占模式下的获得同步状态的线程 setExclusiveOwnerThread(current); return true; } } //如果锁被占用 //判断是否为当前线程再次获取锁 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); //计数 +1 setState(nextc); return true; } return false; }
该方法增加了再次获取同步状态的处理逻辑:通过判断当前线程是否为获取锁的线程来决定获取才做是否成功,如果是获取锁的线程再次请求获取锁,则对同步状态进行计数加1并返回true,表示获取同步状态成功。
成功获取锁的线程再次获取锁,只是增加了同步状态值,这也要求ReentrantLock在释放同步状态时减少同步状态的值,该方法的代码清单如下:
protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; }
如果该锁被获取了n次,那么锁的释放在前(n-1)次必须返回false,而只有同步状态完全释放了才返回true。可以看到该方法将同步状态是否为0作为最终释放的条件,当同步状态为0时,占有线程设置为null,并返回true,表示释放成功。