转载自:https://blog.csdn.net/kiss_the_sun/article/details/50221463

参考文档: http://ifeve.com/java_lock_see4/ 
http://ifeve.com/zookeeper-lock/ 
官网:http://curator.apache.org/curator-recipes/shared-reentrant-lock.html 
http://curator.apache.org/curator-recipes/shared-lock.html 
http://curator.apache.org/curator-recipes/shared-semaphore.html 
http://curator.apache.org/curator-recipes/multi-shared-lock.html

Recipes实现的锁有五种: Shared Reentrant Lock共享可重入锁 , Shared Lock 共享锁(非可重入),Shared Reentrant Read Write Lock共享可重入读写锁, Shared Semaphore共享信号量 , Multi Shared Lock 多共享锁

可重入锁

可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。 
在JAVA环境下 ReentrantLock 和synchronized 都是 可重入锁 
实例如下:

public class Test implements Runnable{

    public synchronized void get(){
System.out.println(Thread.currentThread().getId());
set();
} public synchronized void set(){
System.out.println(Thread.currentThread().getId());
} @Override
public void run() {
get();
}
public static void main(String[] args) {
Test ss=new Test();
new Thread(ss).start();
new Thread(ss).start();
new Thread(ss).start();
}
}
public class Test implements Runnable {
ReentrantLock lock = new ReentrantLock(); public void get() {
lock.lock();
System.out.println(Thread.currentThread().getId());
set();
lock.unlock();
} public void set() {
lock.lock();
System.out.println(Thread.currentThread().getId());
lock.unlock();
} @Override
public void run() {
get();
} public static void main(String[] args) {
Test ss = new Test();
new Thread(ss).start();
new Thread(ss).start();
new Thread(ss).start();
}
}

两个例子的结果都是正确的,即同一线程id被连续输出两次,结果如下: 
Threadid: 8 
Threadid: 8 
Threadid: 10 
Threadid: 10 
Threadid: 9 
Threadid: 9

可重入锁最大的作用是避免死锁

共享可重入锁 Shared Reentrant Lock

共享意味着该锁是全局同步的,任何时刻不会有两个客户端同时持有该锁。可重入意味着同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。

相关的类

  • InterProcessMutex

使用

创建InterProcessMutex实例

public InterProcessMutex(CuratorFramework client,
String path)
Parameters:
client - client
path - the path to lock

acquire()获取锁

有两个acquire()方法可以获取锁

public void acquire()
Acquire the mutex - blocking until it's available. Note: the same thread can call acquire
re-entrantly. Each call to acquire must be balanced by a call to release()

获取锁,一直阻塞到获取到锁为止。获取锁的线程在获取锁后仍然可以调用acquire()获取锁(可重入)。 锁获取使用完后,调用 了几次acquire()就得调用几次release()释放。

public boolean acquire(long time,
TimeUnit unit)
Acquire the mutex - blocks until it's available or the given time expires. Note: the same thread can
call acquire re-entrantly. Each call to acquire that returns true must be balanced by a call to release() Parameters:
time - time to wait
unit - time unit
Returns:
true if the mutex was acquired, false if not

与acquire()类似,等待time * unit时间获取锁,如果仍然没有获取锁,则直接返回false。

release()释放锁

public void release()
Perform one release of the mutex if the calling thread is the same thread that acquired it. If the
thread had made multiple calls to acquire, the mutex will still be held when this method returns.

线程通过acquire()获取锁时,可通过release()进行释放,如果该线程多次调用 了acquire()获取锁,则如果只调用 一次release()该锁仍然会被该线程持有。

注意:InterProcessMutex 实例是可重用的,也就是不需要每次都new一个InterProcessMutex 实例,用同一个实例就好。

Revoking锁撤销

InterProcessMutex 支持锁撤销机制 
可通过调用makeRevocable()将锁设为可撤销的,当另一线程希望你释放该锁时,实例里的listener会被调用。 撤销机制是协作的。

public void makeRevocable(RevocationListener<T> listener)
Make the lock revocable. Your listener will get called when another process/thread wants you to release the lock. Revocation is cooperative.
Parameters:
listener - the listener

可通过调用 静态方法attemptRevoke()要求锁被释放或者撤销。如果该锁上注册有RevocationListener监听,该监听会被调用。

public static void attemptRevoke(CuratorFramework client,
String path)
throws Exception
Utility to mark a lock for revocation. Assuming that the lock has been registered
with a RevocationListener, it will get called and the lock should be released. Note,
however, that revocation is cooperative.
Parameters:
client - the client
path - the path of the lock - usually from something like InterProcessMutex.getParticipantNodes()

错误处理

强烈建议增加ConnectionStateListener监听SUSPENDED和LOST状态变化。如果SUSPENDED连接,不能确定是否仍然持有该锁,除非随后收到了一个RECONNECTED状态。如果LOST连接,将不再持有锁。

代码示例

来自于官网 
共享资源

/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package locking; import java.util.concurrent.atomic.AtomicBoolean; /**
* Simulates some external resource that can only be access by one process at a time
*/
public class FakeLimitedResource
{
private final AtomicBoolean inUse = new AtomicBoolean(false); public void use() throws InterruptedException
{
// in a real application this would be accessing/manipulating a shared resource if ( !inUse.compareAndSet(false, true) )
{
throw new IllegalStateException("Needs to be used by one client at a time");
} try
{
Thread.sleep((long)(3 * Math.random()));
}
finally
{
inUse.set(false);
}
}
}

锁的使用

/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package locking; import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import java.util.concurrent.TimeUnit; public class ExampleClientThatLocks
{
private final InterProcessMutex lock;
private final FakeLimitedResource resource;
private final String clientName; public ExampleClientThatLocks(CuratorFramework client, String lockPath, FakeLimitedResource resource, String clientName)
{
this.resource = resource;
this.clientName = clientName;
lock = new InterProcessMutex(client, lockPath);
} public void doWork(long time, TimeUnit unit) throws Exception
{
if ( !lock.acquire(time, unit) )
{
throw new IllegalStateException(clientName + " could not acquire the lock");
}
try
{
System.out.println(clientName + " has the lock");
resource.use();
}
finally
{
System.out.println(clientName + " releasing the lock");
lock.release(); // always release the lock in a finally block
}
}
}

通过acquire()尝试获取锁,如果一段时间内仍然不能获取锁,直接抛异常。如果获取成功,修改共享资源 。

客户端

/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package locking; import org.apache.curator.utils.CloseableUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.test.TestingServer;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; public class LockingExample
{
private static final int QTY = 5;
private static final int REPETITIONS = QTY * 10; private static final String PATH = "/examples/locks"; public static void main(String[] args) throws Exception
{
// all of the useful sample code is in ExampleClientThatLocks.java // FakeLimitedResource simulates some external resource that can only be access by one process at a time
final FakeLimitedResource resource = new FakeLimitedResource(); ExecutorService service = Executors.newFixedThreadPool(QTY);
final TestingServer server = new TestingServer();
try
{
for ( int i = 0; i < QTY; ++i )
{
final int index = i;
Callable<Void> task = new Callable<Void>()
{
@Override
public Void call() throws Exception
{
CuratorFramework client = CuratorFrameworkFactory.newClient(server.getConnectString(), new ExponentialBackoffRetry(1000, 3));
try
{
client.start(); ExampleClientThatLocks example = new ExampleClientThatLocks(client, PATH, resource, "Client " + index);
for ( int j = 0; j < REPETITIONS; ++j )
{
example.doWork(10, TimeUnit.SECONDS);
}
}
catch ( Throwable e )
{
e.printStackTrace();
}
finally
{
CloseableUtils.closeQuietly(client);
}
return null;
}
};
service.submit(task);
} service.shutdown();
service.awaitTermination(10, TimeUnit.MINUTES);
}
finally
{
CloseableUtils.closeQuietly(server);
}
}
}

起五个线程,即五个客户端,五个客户端分别尝试修改共享资源50次,先是尝试获取锁,修改成功后,释放锁。

共享锁 Shared Lock

与共享可重入锁类似,只不过是不可重入。

相关的类

  • InterProcessSemaphoreMutex

使用

创建实例

public InterProcessSemaphoreMutex(CuratorFramework client,
String path)
Parameters:
client - client
path - the path to lock

获取锁 acquire()

两个方法

public void acquire()
Acquire the mutex - blocking until it's available. Must be balanced by a call to release().

直接获取锁,一直阻塞到获取锁为止,获取完使用完后必须调用 release()释放锁。

错误处理

强烈建议增加ConnectionStateListener监听SUSPENDED和LOST状态变化。如果SUSPENDED连接,不能确定是否仍然持有该锁,除非随后收到了一个RECONNECTED状态。如果LOST连接,将不再持有锁。

代码示例

修改ExampleClientThatLocks.doWork,连续两次acquire:

  public void doWork(long time, TimeUnit unit) throws Exception {
if (!lock.acquire(time, unit)) {
throw new IllegalStateException(clientName + " could not acquire the lock");
}
System.out.println(clientName + " has the lock");
if (!lock.acquire(time, unit)) {
throw new IllegalStateException(clientName + " could not acquire the lock");
}
System.out.println(clientName + " has the lock again"); try {
resource.use(); //access resource exclusively
} finally {
System.out.println(clientName + " releasing the lock");
lock.release(); // always release the lock in a finally block
lock.release(); // always release the lock in a finally block
}
}

注意我们也需要调用release两次。这和JDK的ReentrantLock用法一致。如果少调用一次release,则此线程依然拥有锁。 上面的代码没有问题,我们可以多次调用acquire,后续的acquire也不会阻塞。 将上面的InterProcessMutex换成不可重入锁InterProcessSemaphoreMutex,如果再运行上面的代码,结果就会发现线程被阻塞再第二个acquire上。 也就是此锁不是可重入的。

共享可重入读写锁Shared Reentrant Read Write Lock

读写锁负责管理一对相关的锁,一个负责 读操作,一个负责 写操作。读锁在没有写锁没被使用时能够被 多个读进行使用。但是写锁只能被 一个进得持有。 
只有当写锁释放时,读锁才能被持有,一个拥有写锁的线程可重入读锁,但是读锁却不能进入写锁。 这也意味着写锁可以降级成读锁, 比如请求写锁 —>读锁 —->释放写锁。 从读锁升级成写锁是不行的。

相关的类

  • InterProcessReadWriteLock
  • InterProcessLock

使用

创建实例

public InterProcessReadWriteLock(CuratorFramework client,
String basePath)
Parameters:
client - the client
basePath - path to use for locking

锁的获取

可通过readLock()和writeLock()分别进行锁,再通过acquire()获取锁,具体方法与共享锁类似。

public InterProcessLock readLock()

public InterProcessLock writeLock()

错误处理

强烈建议增加ConnectionStateListener监听SUSPENDED和LOST状态变化。如果SUSPENDED连接,不能确定是否仍然持有该锁,除非随后收到了一个RECONNECTED状态。如果LOST连接,将不再持有锁。

代码示例

修改ExampleClientThatLocks.doWork,先请求一个写锁,再降级成读锁,执行业务处理,然后释放 读写锁。

/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package locking; import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.framework.recipes.locks.InterProcessReadWriteLock; import java.util.concurrent.TimeUnit; public class ExampleClientReadWriteLocks
{
//读写锁
private final InterProcessReadWriteLock lock;
//读锁
private final InterProcessMutex readLock;
//写锁
private final InterProcessMutex writeLock;
private final FakeLimitedResource resource;
private final String clientName; public ExampleClientReadWriteLocks(CuratorFramework client, String lockPath, FakeLimitedResource resource, String clientName)
{
this.resource = resource;
this.clientName = clientName;
lock = new InterProcessReadWriteLock(client, lockPath);
readLock=lock.readLock();
writeLock=lock.writeLock();
} public void doWork(long time, TimeUnit unit) throws Exception
{
if ( !writeLock.acquire(time, unit) )
{
throw new IllegalStateException(clientName + " could not acquire the writelock");
}
System.out.println(clientName + " has the writelock"); if (!readLock.acquire(time, unit)) {
throw new IllegalStateException(clientName + " could not acquire the readLock");
}
System.out.println(clientName + " has the readLock too");
try
{
resource.use();
}
finally
{
System.out.println(clientName + " releasing the lock");
readLock.release(); // always release the lock in a finally block
writeLock.release();
}
}
}

共享信号量Shared Semaphore

一个计数的信号量类似JDK的Semaphore。 JDK中Semaphore维护的一组许可(permits),而Cubator中称之为租约(Lease)。 
有两种方式决定信号号的最大租约数。一种是由用户指定的路径来决定最大租约数,一种是通过SharedCountReader来决定。 
如果不使用SharedCountReader,没有任何一种方法能够检测不同进行使用不同租约数,如进程A使用10个租约,而进程B使用20个租约。*必须保证所有实例使用相同的租约数。* 
acuquire()方法返回的是Lease对象,客户端在使用完后必须要关闭该lease对象(一般在finally中进行关闭),否则该对象会丢失。如果进程session丢失(如崩溃),该客户端拥有的所有lease会被自动关闭,此时其他端能够使用这些lease。

使用的类

  • InterProcessSemaphoreV2
  • Lease
  • SharedCountReader

使用方法

创建实例

构造方法中指定租约数

public InterProcessSemaphoreV2(CuratorFramework client,
String path,
int numberOfLeases)
Parameters:
client - client
path - the path to lock
numberOfLeases - the number of leases allowed by this semaphore
public InterProcessSemaphoreV2(CuratorFramework client,
String path,
SharedCountReader count)
Parameters:
client - the client
path - path for the semaphore
count - the shared count to use for the max leases

获取lease

请求获取lease,如果Semaphore当前的租约不够,该方法会一直阻塞,直到最大租约数增大或者其他客户端释放了一个lease。 
当lease对象获取成功后,处理完成后,客户端必须调用close该lease(可通过return()方法释放lease)。最好在finally块中close。

public Lease acquire()

Acquire a lease. If no leases are available, this method blocks until either the maximum number of
leases is increased or another client/process closes a lease.
The client must close the lease when it is done with it. You should do this in a finally block.
Returns:
the new lease

请求获取多个lease。如果当前semaphore的租约不够,该方法会一直阻塞,直到最大租约数增大或者其他客户端释放了足够的lease。 
当lease对象获取成功后,处理完成后,客户端必须调用close该lease(可通过 returnAll(Collection)方法释放lease)。最好在finally块中close。

public Collection<Lease> acquire(int qty)

Acquire qty leases. If there are not enough leases available, this method blocks until either the
maximum number of leases is increased enough or other clients/processes close enough leases.
The client must close the leases when it is done with them. You should do this in a finally block.
NOTE: You can use returnAll(Collection) for this.
Parameters:
qty - number of leases to acquire
Returns:
the new leases

对应的有阻塞时间的acquire()方法

public Lease acquire(long time,
TimeUnit unit)
Acquire a lease. If no leases are available, this method blocks until either the maximum number of
leases is increased or another client/process closes a lease. However, this method will only block
to a maximum of the time parameters given.
The client must close the lease when it is done with it. You should do this in a finally block.
Parameters:
time - time to wait
unit - time unit
Returns:
the new lease or null if time ran out
public Collection<Lease> acquire(int qty,
long time,
TimeUnit unit)
Acquire qty leases. If there are not enough leases available, this method blocks until either the
maximum number of leases is increased enough or other clients/processes close enough leases. However,
this method will only block to a maximum of the time parameters given. If time expires before all
leases are acquired, the subset of acquired leases are automatically closed.
The client must close the leases when it is done with them. You should do this in a finally block.
NOTE: You can use returnAll(Collection) for this.
Parameters:
qty - number of leases to acquire
time - time to wait
unit - time unit

释放 lease

lease可直接close或者调用以下两种方法:

public void returnAll(Collection<Lease> leases)
public void returnLease(Lease lease)

错误处理

强烈建议增加ConnectionStateListener监听SUSPENDED和LOST状态变化。如果SUSPENDED连接,不能确定是否仍然持有该锁,除非随后收到了一个RECONNECTED状态。如果LOST连接,将不再持有锁。

代码示例

jdk1.7

package locking;

import java.util.Collection;
import java.util.concurrent.TimeUnit; import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreV2;
import org.apache.curator.framework.recipes.locks.Lease;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.test.TestingServer; public class InterProcessSemaphoreExample {
private static final int MAX_LEASE=10;
private static final String PATH="/examples/locks"; public static void main(String[] args) throws Exception {
FakeLimitedResource resource = new FakeLimitedResource();
try (TestingServer server = new TestingServer()) { CuratorFramework client = CuratorFrameworkFactory.newClient(server.getConnectString(), new ExponentialBackoffRetry(1000, 3));
client.start(); InterProcessSemaphoreV2 semaphore = new InterProcessSemaphoreV2(client, PATH, MAX_LEASE);
Collection<Lease> leases = semaphore.acquire(5);
System.out.println("get " + leases.size() + " leases");
Lease lease = semaphore.acquire();
System.out.println("get another lease"); resource.use(); Collection<Lease> leases2 = semaphore.acquire(5, 10, TimeUnit.SECONDS);
System.out.println("Should timeout and acquire return " + leases2); System.out.println("return one lease");
semaphore.returnLease(lease);
System.out.println("return another 5 leases");
semaphore.returnAll(leases);
}
} }

首先我们先获得了5个租约, 最后我们把它还给了semaphore。 接着请求了一个租约,因为semaphore还有5个租约,所以请求可以满足,返回一个租约,还剩4个租约。 然后再请求一个租约,因为租约不够,阻塞到超时,还是没能满足,返回结果为null。

上面说讲的锁都是公平锁(fair)。 总ZooKeeper的角度看, 每个客户端都按照请求的顺序获得锁。 相当公平。

多锁对象

一个维护多个锁对象的容器。当调用 acquire()时,获取容器中所有的锁对象,请求失败时,释放所有锁对象。同样调用release()也会释放所有的锁。

相关的类

  • InterProcessMultiLock
  • InterProcessLock

使用方法

创建实例

public InterProcessMultiLock(List<InterProcessLock> locks)
Creates a multi lock of any type of inter process lock
Parameters:
locks - the locks
public InterProcessMultiLock(CuratorFramework client,
List<String> paths)
Creates a multi lock of InterProcessMutexes
Parameters:
client - client
paths - list of paths to manage in the order that they are to be locked

第二个构造方法,List paths将要将要被锁的路径 。

获取锁

锁的获取与共享锁类似 。通过调用 acquire()可获取所有的锁,失败时,所获得的锁将被释放。同样,调用 release()时会释放所有的锁(失败时忽略)。

错误处理

强烈建议增加ConnectionStateListener监听SUSPENDED和LOST状态变化。如果SUSPENDED连接,不能确定是否仍然持有该锁,除非随后收到了一个RECONNECTED状态。如果LOST连接,将不再持有锁。

代码示例

package locking;

import java.util.Arrays;
import java.util.concurrent.TimeUnit; import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessLock;
import org.apache.curator.framework.recipes.locks.InterProcessMultiLock;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.test.TestingServer; public class ExampleInterProcessMultiLock {
private static final String PATH1="/example/lock1";
private static final String PATH2="/example/lock2"; public static void main(String[] args) throws Exception{
FakeLimitedResource resource = new FakeLimitedResource();
try(TestingServer server=new TestingServer()){
CuratorFramework client = CuratorFrameworkFactory.newClient(server.getConnectString(), new ExponentialBackoffRetry(1000, 3));
client.start(); InterProcessLock lock1 = new InterProcessMutex(client, PATH1);
InterProcessLock lock2 = new InterProcessSemaphoreMutex(client, PATH2); InterProcessMultiLock lock = new InterProcessMultiLock(Arrays.asList(lock1, lock2)); if (!lock.acquire(10, TimeUnit.SECONDS)) {
throw new IllegalStateException("could not acquire the lock");
}
System.out.println("has the lock"); System.out.println("has the lock1: " + lock1.isAcquiredInThisProcess());
System.out.println("has the lock2: " + lock2.isAcquiredInThisProcess()); try {
resource.use(); //access resource exclusively
} finally {
System.out.println("releasing the lock");
lock.release(); // always release the lock in a finally block
}
System.out.println("has the lock1: " + lock1.isAcquiredInThisProcess());
System.out.println("has the lock2: " + lock2.isAcquiredInThisProcess()); }
}
}

新建一个InterProcessMultiLock, 包含一个重入锁和一个非重入锁。 调用acquire后可以看到线程同时拥有了这两个锁。 调用release看到这两个锁都被释放了。

再重申一遍, 强烈推荐使用ConnectionStateListener监控连接的状态。

05-12 13:02