学习
https://time.geekbang.org/column/article/83267
https://time.geekbang.org/column/article/83087
并发编程有三个核心的问题

1、分工

指的是如何高效地拆解任务并分配给线程,Java SDK 并发包里的 Executor、Fork/Join、Future 本质上都是一种分工方法。除此之外,并发编程领域还总结了一些设计模式,基本上都是和分工方法相关的,例如生产者 - 消费者、Thread-Per-Message、Worker Thread 模式等都是用来指导你如何分工的。

2、同步

分工之后就要考虑多个线程如何执行,期间就涉及到了同步
同步可以总结为:当某个条件不满足时,线程需要等待,当某个条件满足时,线程需要被唤醒执行
例如,在生产者 - 消费者模型里,也有类似的描述,“当队列满时,生产者线程等待,当队列不满时,生产者线程需要被唤醒执行;当队列空时,消费者线程等待,当队列不空时,消费者线程需要被唤醒执行。”

3、互斥

分工、同步主要强调的是性能,但是我们还要保证正确性,也就是线程安全,
保证同一时刻只允许一个线程访问共享资源
并发程序里,当多个线程同时访问同一个共享变量的时候,结果是不确定的。不确定,则意味着可能正确,也可能错误,事先是不知道的。M而导致不确定的主要源头是可见性问题、有序性问题和原子性问题,为了解决这三个问题引入了JMM

解决互斥基本使用的是加锁, synchronized、SDK 里的各种 Lock 都能解决互斥问题。虽说锁解决了安全性问题,但同时也带来了性能问题

但是也有其他的方案避免因为共享变量导致线程安全问题,比如Thread Local 和 final 关键字,还有一种 Copy-on-write 的模式。

image.png

扩展

  1. 什么场景下使用CountDownLatch

比如多线程解析excel的多个sheet,可以开启多个线程去解析,每个线程解析一个sheet,解析完成之后执行countDown方法,在最后执行await()方法,直到所有线程执行完成
参考自https://ifeve.com/talk-concurrency-countdownlatch/

  1. countDown代码演示 相当于一场考试。所有的学生交了试卷之后,老师才开始离开教师.

代码来自https://www.cnblogs.com/bqcoder/p/6089101.html

  1. public static void testCountDownLatch(){
  2. int threadCount = 10;
  3. final CountDownLatch latch = new CountDownLatch(threadCount);
  4. for(int i=0; i< threadCount; i++){
  5. new Thread(()->{
  6. System.out.println("线程" + Thread.currentThread().getId() + "开始出发");
  7. try {
  8. Thread.sleep(1000);
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. System.out.println("线程" + Thread.currentThread().getId() + "已到达终点");
  13. latch.countDown();
  14. }).start();
  15. }
  16. try {
  17. //会等待所有的线程执行完毕
  18. latch.await();
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. System.out.println("10个线程已经执行完毕!开始计算排名");
  23. }
  1. final 关键字和多线程的关系(happens-before原则)

    1. public class FinalTest {
    2. int i;//普通变量
    3. final int j;
    4. static FinalTest obj;
    5. public FinalTest(){
    6. i = 1;
    7. j = 2;
    8. }
    9. public static void writer(){
    10. obj = new FinalTest();
    11. }
    12. public static void reader(){
    13. FinalTest tets = obj;//读对象引用
    14. int a = tets.i;//a 和b 不一定等于1,2 因为会有构造函数溢出的可能。
    15. int b = tets.j;
    16. }
    17. }