Java的可重入鎖和不可重入鎖

前言

    最近在用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

    因此這個不可重入是對同一個線程而言,可否第二次獲取鎖,下面是另外一篇博客總結的:分佈式

  • 可重入鎖:能夠再次進入方法A,就是說在釋放鎖前此線程能夠再次進入方法A(方法A遞歸)。
  • 不可重入鎖(自旋鎖):不能夠再次進入方法A,也就是說得到鎖進入方法A是此線程在釋放鎖錢惟一的一次進入方法A。

   那這兩種鎖除了在可能會致使死鎖方面的區別外,效率有差異了,我就利用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測試,其代碼實現也很複雜,能夠試試用其餘的工具測一下二者的區別。

相關文章
相關標籤/搜索