一直对zookeeper的分布式锁没有什么概念,今天研究了一下,在此发表下心得.此处只说可重入锁.别的网上讲解很多
先说一下我的需求:要在多线程的情况下,同时对一个文件读写,并且保证读写的原子性和一致性(本人基本概念表达的不是很准确的地方见谅),我喜欢用代码说话.
先看文件D:\\a.txt,此处用a.txt当做一个资源.
此时只有第一行有个*,以后每一行都要多一个*,并且要在多线程的情况下进行读写
我的代码如下,代码结构来自https://blog.csdn.net/haoyuyang/article/details/53469269
pom.xml如下:
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-client</artifactId>
<version>2.9.0</version>
</dependency>
一下代码是包含了zookeeper的API,一开始的几个测试代码是没有用的zookeeper的,大家可以先把CuratorFramework相关的删掉,从头来,很简单,在此就不繁琐的列代码了,大家在下面代码上自由发挥下
package test.zklock;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class FakeLimitedResource {
public void use() throws InterruptedException{
FileReader read = null;
BufferedReader bufferedReader = null;
FileWriter fileWriter = null;
try {
read = new FileReader(new File("D:\\a.txt"));
bufferedReader =new BufferedReader(read);
String str = null;
List<String> strList = new ArrayList<String>();
while((str=bufferedReader.readLine()) != null){
strList.add(str);
}
String outStr = " \n" + strList.get(strList.size()-1) + "*";
fileWriter =new FileWriter(new File("D:\\a.txt"), true);
fileWriter.write(outStr);
fileWriter.flush();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
if(bufferedReader != null){
try {
bufferedReader.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(fileWriter != null){
try {
fileWriter.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
package test.zklock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessLock;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import com.sun.xml.internal.txw2.IllegalAnnotationException;
public class ExampleClientThatLock {
private final InterProcessMutex lock;
private final String clientName;
private final FakeLimitedResource resource;
private Lock lk = new ReentrantLock();
public ExampleClientThatLock(CuratorFramework curator,String clientName, FakeLimitedResource resource,String path){
this.lock = new InterProcessMutex(curator, path);
this.clientName = clientName;
this.resource = resource;
}
public void doWork(long time,TimeUnit timeUnit) throws Exception{
// if(!lock.acquire(time, timeUnit)){
// throw new IllegalStateException("clientName: " + this.clientName + "cloudn't acquire the lock");
// }
try {
//1-使用ReentrantLock
//this.lk.lock();
System.out.println(this.clientName + "get lock");
resource.use();
} finally {
System.out.println(this.clientName + "releasing the lock");
//this.lock.release();
//1-释放ReentrantLock
//this.lk.unlock();
}
}
}
package test.zklock;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.utils.CloseableUtils;
public class InterProcessMutexExample {
private static final int QTY=5;
private static final int REPETITIONS = QTY*2;
private final static String CONNECT_ADDRESS = "172.25.90.10:2181,172.25.90.11:2181,172.25.90.12:2181";
private final static String PATH = "/test02/a";
/**
* @param args
*/
public static void main(String[] args) {
try {
final FakeLimitedResource resource = new FakeLimitedResource();
ExecutorService executor = Executors.newFixedThreadPool(QTY);
for (int i = 0; i < QTY; i++) {
final int index = i;
Callable<Void> task = new Callable<Void>() {
@Override
public Void call() throws Exception {
CuratorFramework curator = CuratorFrameworkFactory.newClient(CONNECT_ADDRESS, new ExponentialBackoffRetry(1000, 10));
try {
curator.start();
final ExampleClientThatLock client = new ExampleClientThatLock(curator, "client_" + index, resource, PATH);
for (int j = 0; j < REPETITIONS; j++) {
client.doWork(10, TimeUnit.SECONDS);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
CloseableUtils.closeQuietly(curator);
}
return null;
}
};
executor.submit(task);
}
executor.shutdown();
executor.awaitTermination(500, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
建议大家把代码跑一下,重点代码在此处:doWork 方法.一下没有用到zk的时候可以忽略zk相关的代码,大家试着
第一步: 不加锁的情况下,打印结果如下:具体
a.txt结果如下
加上synchronized结果也是如上面(a.txt可能有点变化但是想要的结果没出来)
加上 ReentrantLock如下
依然没有效果.为什么lock没有起作用呢?
可能InterProcessMutexExample代码有点难懂,咱们换个简单的
package test.zklock;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
public class LockTest {
private static final int QTY=5;
private static final int REPETITIONS = QTY*2;
private final static String CONNECT_ADDRESS = "172.25.90.10:2181,172.25.90.11:2181,172.25.90.12:2181";
private final static String PATH = "/test02/a";
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
CuratorFramework curator = CuratorFrameworkFactory.newClient(CONNECT_ADDRESS, new ExponentialBackoffRetry(1000, 10));
final FakeLimitedResource resource = new FakeLimitedResource();
final ExampleClientThatLock client = new ExampleClientThatLock(curator, "client_" + 1, resource, PATH);
for (int j = 0; j < REPETITIONS; j++) {
try {
client.doWork(10, TimeUnit.SECONDS);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
CuratorFramework curator = CuratorFrameworkFactory.newClient(CONNECT_ADDRESS, new ExponentialBackoffRetry(1000, 10));
final FakeLimitedResource resource = new FakeLimitedResource();
final ExampleClientThatLock client = new ExampleClientThatLock(curator, "client_" + 2, resource, PATH);
for (int j = 0; j < REPETITIONS; j++) {
try {
client.doWork(10, TimeUnit.SECONDS);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
t1.start();
t2.start();
}
}
此处不过多解释为什么它俩的作用一样,如有不懂请学习下Future模式和jdk自带的Future
结果:
此处是20行,最早的是50行,这个相信大家都能懂,如果这个不懂,需要补习基础了呦
为什么还是不一样呢?
来看下面这个代码:
package test.zklock;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
public class LockTest {
private static final int QTY=5;
private static final int REPETITIONS = QTY*2;
private final static String CONNECT_ADDRESS = "172.25.90.10:2181,172.25.90.11:2181,172.25.90.12:2181";
private final static String PATH = "/test02/a";
public static void main(String[] args) {
CuratorFramework curator = CuratorFrameworkFactory.newClient(CONNECT_ADDRESS, new ExponentialBackoffRetry(1000, 10));
final FakeLimitedResource resource = new FakeLimitedResource();
final ExampleClientThatLock client = new ExampleClientThatLock(curator, "client_" + 1, resource, PATH);
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < REPETITIONS; j++) {
try {
client.doWork(10, TimeUnit.SECONDS);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < REPETITIONS; j++) {
try {
client.doWork(10, TimeUnit.SECONDS);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
t1.start();
t2.start();
}
}
结果:
为什么这次可以了呢?
大家注意看下,两处的代码不一样的地方在,线程用的对象上面,不可以的线程用的对象都是各自线程自有的对象,而最后一次用的是一个对象.
此时回想一下需求,大家是为了保证a.txt文件的线程安全,有人会说,我用最后一种方式不就可以了,此处回到分布式的重点上来看,如果是2个不同应用去读写a.txt文件呢?
此时我们用下下面的代码:此处用到了zookeeper的分布式锁InterProcessMutex
大家可以把项目再复制一份,2个同时运行试试
此处设置大一点可以看到运行结果
太大了,此处不截图了,大家试试
关于InterProcessMutex的原理,大家可以看下https://blog.csdn.net/qiangcuo6087/article/details/79067136