java不可重入 锁_Java的可重入锁和不可重入锁

前言

最近在用Apache的Zookeeper客户端库Curator,Curator实现了一套的分布式锁,有可重入和不可重入,想起其实在单机环境下,Java提供的synchronized 和 ReentrantLock的锁工具,这两个都是可重入锁,所以可重入锁和不可重入锁有什么区别呢,带着这个问题,去网上找答案。

主题

很多的博客上都是列了怎么实现这两种锁,例如像下面的两段代码:

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();

}

}

上面实现的是一个不可重入锁,下面这段实现的是一个可重入锁:

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();

}

}

}

}

从代码实现来看,可重入锁增加了两个状态,锁的计数器和被锁的线程,实现基本上和不可重入的实现一样,如果不同的线程进来,这个锁是没有问题的,但是如果进行递归计算的时候,如果加锁,不可重入锁就会出现死锁的问题。

所以这个不可重入是对同一个线程而言,能否第二次获取锁,下面是另一篇博客总结的:

可重入锁:可以再次进入方法A,就是说在释放锁前此线程可以再次进入方法A(方法A递归)。

不可重入锁(自旋锁):不可以再次进入方法A,也就是说获得锁进入方法A是此线程在释放锁钱唯一的一次进入方法A。

那这两种锁除了在可能会导致死锁方面的区别外,效率有差别了,我就利用Curator做了一个实验,实验的代码如下:

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(1000));

List> callables = Lists.newArrayList();

long start = System.currentTimeMillis();

for (int i = 0; i < 100; i++) {

Callable runnable = new Callable() {

//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> futures = pool.invokeAll(callables);

for (Future 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 = 91544

可重入锁的花费时间是:time spend = 52796

我在想为什么这两种的实现的效率会差这么多,于是去看了下两种锁的源码,第一个是可重入锁的关键实现代码,第二个是不可重入的关键实现代码:

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 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 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 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 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 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测试,其代码实现也很复杂,可以试试用其他的工具测一下两者的区别。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值