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

展开阅读全文

Git 实用技巧

11-24
这几年越来越多的开发团队使用了Git,掌握Git的使用已经越来越重要,已经是一个开发者必备的一项技能;但很多人在刚开始学习Git的时候会遇到很多疑问,比如之前使用过SVN的开发者想不通Git提交代码为什么需要先commit然后再去push,而不是一条命令一次性搞定; 更多的开发者对Git已经入门,不过在遇到一些代码冲突、需要恢复Git代码时候就不知所措,这个时候哪些对 Git掌握得比较好的少数人,就像团队中的神一样,在队友遇到 Git 相关的问题的时候用各种流利的操作来帮助队友于水火。 我去年刚加入新团队,发现一些同事对Git的常规操作没太大问题,但对Git的理解还是比较生疏,比如说分支和分支之间的关联关系、合并代码时候的冲突解决、提交代码前未拉取新代码导致冲突问题的处理等,我在协助处理这些问题的时候也记录各种问题的解决办法,希望整理后通过教程帮助到更多对Git操作进阶的开发者。 本期教程学习方法分为“掌握基础——稳步进阶——熟悉协作”三个层次。从掌握基础的 Git的推送和拉取开始,以案例进行演示,分析每一个步骤的操作方式和原理,从理解Git 工具的操作到学会代码存储结构、演示不同场景下Git遇到问题的不同处理方案。循序渐进让同学们掌握Git工具在团队协作中的整体协作流程。 在教程中会通过大量案例进行分析,案例会模拟在工作中遇到的问题,从最基础的代码提交和拉取、代码冲突解决、代码仓库的数据维护、Git服务端搭建等。为了让同学们容易理解,对Git简单易懂,文章中详细记录了详细的操作步骤,提供大量演示截图和解析。在教程的最后部分,会从提升团队整体效率的角度对Git工具进行讲解,包括规范操作、Gitlab的搭建、钩子事件的应用等。 为了让同学们可以利用碎片化时间来灵活学习,在教程文章中大程度降低了上下文的依赖,让大家可以在工作之余进行学习与实战,并同时掌握里面涉及的Git不常见操作的相关知识,理解Git工具在工作遇到的问题解决思路和方法,相信一定会对大家的前端技能进阶大有帮助。
©️2020 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值