這幾天在看分佈式鎖,照着博客手寫一個分佈式鎖也好, 直接用框架也好,怎麼驗證寫的是否保證線程安全?
有個傳統的就是多線程循環對一個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 並沒有關閉. 所以出現這個問題.