最近在用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測試,其代碼實現也很複雜,能夠試試用其餘的工具測一下二者的區別。