代碼實現用直觀的方式來檢查鎖Lock是否線程安全

代碼實現用直觀的方式來檢查鎖Lock是否線程安全


這幾天在看分佈式鎖,照着博客手寫一個分佈式鎖也好, 直接用框架也好,怎麼驗證寫的是否保證線程安全?

有個傳統的就是多線程循環對一個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 並沒有關閉. 所以出現這個問題.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章