最近在用Apache的Zookeeper客户端库Curator,Curator实现了一套的分布式锁,有可重入和不可重入,想起其实在单机环境下,Java提供的synchronized 和 ReentrantLock的锁工具,这两个都是可重入锁,因此可重入锁和不可重入锁有什么区别呢,带着这个问题,去网上找答案。java
不少的博客上都是列了怎么实现这两种锁,例如像下面的两段代码:node
public class Lock{ private boolean isLocked = false; public synchronized void lock() throws InterruptedException{ while(isLocked){ wait(); } isLocked = true; } public synchronized void unlock(){ isLocked = false; notify(); } }
上面实现的是一个不可重入锁,下面这段实现的是一个可重入锁:session
public class Lock{ boolean isLocked = false; Thread lockedBy = null; int lockedCount = 0; public synchronized void lock() throws InterruptedException{ Thread callingThread = Thread.currentThread(); while(isLocked && lockedBy != callingThread){ wait(); } isLocked = true; lockedCount++; lockedBy = callingThread; } public synchronized void unlock(){ if(Thread.curentThread() == this.lockedBy){ lockedCount--; if(lockedCount == 0){ isLocked = false; notify(); } } } }
从代码实现来看,可重入锁增长了两个状态,锁的计数器和被锁的线程,实现基本上和不可重入的实现同样,若是不一样的线程进来,这个锁是没有问题的,可是若是进行递归计算的时候,若是加锁,不可重入锁就会出现死锁的问题。app
因此这个不可重入是对同一个线程而言,可否第二次获取锁,下面是另外一篇博客总结的:分布式
那这两种锁除了在可能会致使死锁方面的区别外,效率有差异了,我就利用Curator作了一个实验,实验的代码以下:ide
private int count = 0; @Test public void testDistribute() throws InterruptedException, ExecutionException { startClient(); ThreadPoolExecutor pool = new ThreadPoolExecutor(4, Runtime.getRuntime().availableProcessors(), 5000, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1000)); List<Callable<Object>> callables = Lists.newArrayList(); long start = System.currentTimeMillis(); for (int i = 0; i < 100; i++) { Callable<Object> runnable = new Callable<Object>() { //InterProcessMutex lock = new InterProcessMutex(client,ZOOKEEPER_PATH); //可重入锁 InterProcessSemaphoreMutex lock = new InterProcessSemaphoreMutex(client,ZOOKEEPER_PATH); //不可重入锁 @Override public Object call() { String name = Thread.currentThread().getName(); System.out.println("current thread name is " + name); try { if (lock.acquire(10*1000,TimeUnit.SECONDS)) { count ++; Thread.sleep(500); } } catch (Exception e) { System.out.println("====" + e.getMessage()); } finally { try { lock.release(); } catch (Exception e) { System.out.println("===== lock release "); } } return count; } }; callables.add(runnable); } List<Future<Object>> futures = pool.invokeAll(callables); for (Future<Object> f: futures ) { Object o = f.get(); System.out.println("future get is " + o); } long end = System.currentTimeMillis(); System.out.println("time spend = " + (end - start)); } /** * must be priority running in testcase */ private void startClient() { RetryPolicy policy = new ExponentialBackoffRetry(SLEEP_TIME, MAX_RETRIES); client = CuratorFrameworkFactory.newClient(ZOOKEEPER_ADDRESS, policy); client.start(); }
在跑上面的测试用例的时候,请分别放开上面的可重入锁和不可重入锁:工具
不可重入锁的花费的时间是:time spend = 91544oop
可重入锁的花费时间是:time spend = 52796测试
我在想为何这两种的实现的效率会差这么多,因而去看了下两种锁的源码,第一个是可重入锁的关键实现代码,第二个是不可重入的关键实现代码:ui
private boolean internalLock(long time, TimeUnit unit) throws Exception { Thread currentThread = Thread.currentThread(); InterProcessMutex.LockData lockData = (InterProcessMutex.LockData)this.threadData.get(currentThread); if(lockData != null) { lockData.lockCount.incrementAndGet(); return true; } else { String lockPath = this.internals.attemptLock(time, unit, this.getLockNodeBytes()); if(lockPath != null) { InterProcessMutex.LockData newLockData = new InterProcessMutex.LockData(currentThread, lockPath, null); this.threadData.put(currentThread, newLockData); return true; } else { return false; } } }
public Collection<Lease> acquire(int qty, long time, TimeUnit unit) throws Exception { long startMs = System.currentTimeMillis(); boolean hasWait = (unit != null); long waitMs = hasWait ? TimeUnit.MILLISECONDS.convert(time, unit) : 0; Preconditions.checkArgument(qty > 0, "qty cannot be 0"); ImmutableList.Builder<Lease> builder = ImmutableList.builder(); boolean success = false; try { while ( qty-- > 0 ) { int retryCount = 0; long startMillis = System.currentTimeMillis(); boolean isDone = false; while ( !isDone ) { switch ( internalAcquire1Lease(builder, startMs, hasWait, waitMs) ) { case CONTINUE: { isDone = true; break; } case RETURN_NULL: { return null; } case RETRY_DUE_TO_MISSING_NODE: { // gets thrown by internalAcquire1Lease when it can't find the lock node // this can happen when the session expires, etc. So, if the retry allows, just try it all again if ( !client.getZookeeperClient().getRetryPolicy().allowRetry(retryCount++, System.currentTimeMillis() - startMillis, RetryLoop.getDefaultRetrySleeper()) ) { throw new KeeperException.NoNodeException("Sequential path not found - possible session loss"); } // try again break; } } } } success = true; } finally { if ( !success ) { returnAll(builder.build()); } } return builder.build(); } private InternalAcquireResult internalAcquire1Lease(ImmutableList.Builder<Lease> builder, long startMs, boolean hasWait, long waitMs) throws Exception { if ( client.getState() != CuratorFrameworkState.STARTED ) { return InternalAcquireResult.RETURN_NULL; } if ( hasWait ) { long thisWaitMs = getThisWaitMs(startMs, waitMs); if ( !lock.acquire(thisWaitMs, TimeUnit.MILLISECONDS) ) { return InternalAcquireResult.RETURN_NULL; } } else { lock.acquire(); } Lease lease = null; try { PathAndBytesable<String> createBuilder = client.create().creatingParentContainersIfNeeded().withProtection().withMode(CreateMode.EPHEMERAL_SEQUENTIAL); String path = (nodeData != null) ? createBuilder.forPath(ZKPaths.makePath(leasesPath, LEASE_BASE_NAME), nodeData) : createBuilder.forPath(ZKPaths.makePath(leasesPath, LEASE_BASE_NAME)); String nodeName = ZKPaths.getNodeFromPath(path); lease = makeLease(path); if ( debugAcquireLatch != null ) { debugAcquireLatch.await(); } try { synchronized(this) { for(;;) { List<String> children; try { children = client.getChildren().usingWatcher(watcher).forPath(leasesPath); } catch ( Exception e ) { if ( debugFailedGetChildrenLatch != null ) { debugFailedGetChildrenLatch.countDown(); } returnLease(lease); // otherwise the just created ZNode will be orphaned causing a dead lock throw e; } if ( !children.contains(nodeName) ) { log.error("Sequential path not found: " + path); returnLease(lease); return InternalAcquireResult.RETRY_DUE_TO_MISSING_NODE; } if ( children.size() <= maxLeases ) { break; } if ( hasWait ) { long thisWaitMs = getThisWaitMs(startMs, waitMs); if ( thisWaitMs <= 0 ) { returnLease(lease); return InternalAcquireResult.RETURN_NULL; } wait(thisWaitMs); } else { wait(); } } } } finally { client.removeWatchers(); } } finally { lock.release(); } builder.add(Preconditions.checkNotNull(lease)); return InternalAcquireResult.CONTINUE; }
首先可重入锁的关键代码逻辑很是简单,并且使用了Atomic原子操做,效率很是高,可是不可重入锁代码量很是大,为了实现一个相似于Semaphore的工具,进行不少的判断,效率很是低,有兴趣的能够升入研究下这两种锁。
重入锁和不可重入锁主要的差异在对相同线程是否可以重复获取,从效率来讲,不可重入锁效率更高,固然这个是用Curator client测试,其代码实现也很复杂,能够试试用其余的工具测一下二者的区别。