代码实现用直观的方式来检查锁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 并没有关闭. 所以出现这个问题.

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