这几天在看分布式锁,照着博客手写一个分布式锁也好, 直接用框架也好,怎么验证写的是否保证线程安全?
注意, 其实下面这种方式并不能很好的测试 分布式 锁 中的分布式效果, 因为下面的前提都是操作一个机器上的文件, 而且如果是单机运行多实例, 也会出现多个进程同时操作文件出现异常
有个传统的就是多线程循环对一个int变量进行 i++,然后看最后的结果是否符合预期。
int n=0;
for(int i=0;i<1000;i++){
n++;
}
//最后判断
找到了另一个方法,先看效果:
没错, 跟循环i++差不多, 这个是循环取最后一行, 拼接字符串加一个星号, 在写入到文件.
代码
操作读写文件的任务类
package com.zgd.demo.thread.lock;
import com.google.common.collect.Lists;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;
public class SimpleWorkTemplate {
private static final String FILE_PATH = "D:\\a.txt";
public static void createNewFileWithStar() throws IOException {
boolean b = Files.notExists(Paths.get(FILE_PATH));
if (b) {
Files.createFile(Paths.get(FILE_PATH));
}
Files.write(Paths.get(FILE_PATH), "*".getBytes(StandardCharsets.UTF_8), StandardOpenOption.TRUNCATE_EXISTING);
//这种方式会有一个回车
// Files.write(Paths.get("D:\\a.txt"), Lists.newArrayList("*"), StandardOpenOption.TRUNCATE_EXISTING);
System.out.println("初始化成功..");
}
public static void doWork() throws Exception {
Path path = Paths.get(FILE_PATH);
try (InputStream in = Files.newInputStream(path, StandardOpenOption.READ);
BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
OutputStream out = Files.newOutputStream(path, StandardOpenOption.APPEND, StandardOpenOption.WRITE);
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))
) {
String str;
String lastLine = null;
while ((str = reader.readLine()) != null) {
lastLine = str;
}
String outStr = lastLine + "*";
writer.newLine();
writer.append(outStr);
writer.flush();
}
}
/**这个多线程下会出现 "另一个程序正在使用此文件,进程无法访问" 的问题,IO流并没有及时关闭
* @throws IOException
*/
public static void doWork3() throws IOException {
Path path = Paths.get("D:\\a.txt");
List<String> lines = Files.readAllLines(path);
String str = lines.stream().skip(lines.size() - 1).findFirst().get();
String outStr = " \n" + str + "*";
Files.write(path, Lists.newArrayList(outStr),StandardOpenOption.APPEND);
}
public static void doWork2() throws Exception {
try (FileReader read = new FileReader(new File(FILE_PATH));
BufferedReader bufferedReader = new BufferedReader(read);
FileWriter fileWriter = new FileWriter(new File(FILE_PATH), true);
) {
String str;
List<String> strList = new ArrayList<>();
while ((str = bufferedReader.readLine()) != null) {
strList.add("\n" + str);
}
String outStr = strList.get(strList.size() - 1) + "*";
fileWriter.write(outStr);
fileWriter.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试类
package com.zgd.demo.thread.lock;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class TestLock {
/**
* 线程数量
*/
private static final int QTY = 50;
/**
* 每个线程执行次数
*/
private static final int REPETITIONS = 2;
private static final Lock lock = new ReentrantLock(true);
/**
* @param args
*/
public static void main(String[] args) throws IOException {
testNoLock();
}
public static void testNoLock() throws IOException {
process(null);
}
public static void testLock(Lock lock) throws IOException {
process(lock);
}
private static void process(Lock lock) throws IOException {
ThreadPoolExecutor pool = new ThreadPoolExecutor(QTY, QTY, 0, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
new ThreadFactoryBuilder().setNameFormat( "test-lock-%d").build(),
new ThreadPoolExecutor.AbortPolicy());
SimpleWorkTemplate.createNewFileWithStar();
CountDownLatch latch = new CountDownLatch(1);
try {
for (int i = 0; i < QTY; i++) {
pool.submit(() -> {
try {
latch.await();
for (int j = 0; j < REPETITIONS; j++) {
doWork(lock);
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
} finally {
System.out.println("开始");
latch.countDown();
pool.shutdown();
try {
pool.awaitTermination(500, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束");
}
}
public static void doWork( Lock lock) throws Exception {
if (lock != null) {
lock.lock();
log.info(Thread.currentThread().getName() + " LLLLLLLLLLLLock");
}
try {
SimpleWorkTemplate.doWork();
} finally {
if (lock != null) {
log.info(Thread.currentThread().getName() + " RRRRRRRRRRRRRlease");
lock.unlock();
}
}
}
}
测试
不加锁
试下不加锁的情况, main方法中调用testNoLock方法;
加锁
试下用jdk自带的可重入锁, ReentrantLock
main方法调用testLock
private static final Lock lock = new ReentrantLock(true);
/**
* @param args
*/
public static void main(String[] args) throws IOException {
testLock(lock);
}
补充
这里也有几个坑,原本我是用java7的Files工具类来实现文件读写, 结果抛出了异常,另一个程序正在使用此文件,进程无法访问
的问题, 说明方法执行完以后IO流并没有及时关闭.
看下Files的write方法
Files.write(path, Lists.newArrayList(outStr),StandardOpenOption.APPEND);
点进去Files类
public static Path write(Path path, Iterable<? extends CharSequence> lines,
Charset cs, OpenOption... options)
throws IOException
{
// ensure lines is not null before opening file
Objects.requireNonNull(lines);
CharsetEncoder encoder = cs.newEncoder();
OutputStream out = newOutputStream(path, options);
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, encoder))) {
for (CharSequence line: lines) {
writer.append(line);
writer.newLine();
}
}
return path;
}
也就是OutputStream 并没有关闭. 所以出现这个问题.