美文网首页
Java并发学习笔记

Java并发学习笔记

作者: 可乐zzz | 来源:发表于2019-07-17 15:50 被阅读0次

    最近都在看极客时间的《Java并发编程》这一课程,看了好一阵,有些明白,有些混沌。于是想着自己整理一版,根据自己的理解做一点笔记。

    先罗列了一个笔记大纲,从整体去看,如何去学习。

    大纲.png
    并发定义

    并发经常和另一个概念被一同提起-并行。
    简单点说,并发与并行的区别就是,并行是真正的同一时刻干了几件事,并发只是看上去一段时间内干了几件事。两者的时间维度不一样,并发是“一段时间”这一维度。
    并发是通过线程切换可以在同一段时间内干几件事。而线程切换就会带来一些问题。

    带来的问题
    • 可见性问题
      可见性问题:一个线程对一个变量的修改对于另一个线程是可见的。而并发可能导致一个线程对变量的修改对于另一个线程是不可见的。
      具体见下面这个常见的例子-计数器:
    public class Test {
      private long count = 0;
      private void add10K() {
        int idx = 0;
        while(idx++ < 10000) {
          count += 1;
        }
      }
      public static long calc() {
        final Test test = new Test();
        // 创建两个线程,执行 add() 操作
        Thread th1 = new Thread(()->{
          test.add10K();
        });
        Thread th2 = new Thread(()->{
          test.add10K();
        });
        // 启动两个线程
        th1.start();
        th2.start();
        // 等待两个线程执行结束
        th1.join();
        th2.join();
        return count;
      }
    }
    
    

    直觉count是20000,但是结果是10000~20000之间的一个数。
    导致可见性问题的原因:


    cpu内存与主内存图.png

    关于可见性问题,今天刚刚细细看了一下,是由于cpu缓存与内存不一致导致的,那么单核应该是不会存在可见性问题,另外多个线程如果在同一个cpu上运行应该也是不会产生可见性问题,因为大家都是读取的同一个cpu缓存。

    • 原子性问题
      原子性问题:一个操作或多个操作在cpu执行过程中被执行不被中断
      我们知道I/O的读取是很慢的,cpu相对来说快很多,于是我们在做一个I/O操作时可能会要等待很久,这段时间内cpu是空闲的,于是有人提出多线程分时复用这个概念,可以在等待I/O的同时,让出cpu执行别的操作,就好像早上热牛奶这一操作要等很久,于是同时你可以洗漱。你就是cpu,热牛奶就是读取I/O。于是呢,就会出现线程切换,这样就会导致操作被中断。

    例如:count+=1这一操作


    原子性问题例子.png

    如上图如果有两个线程同时执行count+=1这一操作,有可能出现线程切换,那么会导致数据错乱。

    • 有序性问题
      例如程序中:“a=6;b=7;”编译器优化后可能变成“b=7;a=6" 。这个主要是编译器优化导致的,关于编译器优化这一点我还是不是很理解。
    以上就是关于右边并发定义,问题及问题产生的原因这三部分的叙述。
    解决方案

    如上所知,可见性问题是缓存导致,原子性问题是线程切换导致,有序性问题是编译器优化导致。那么解决方案也是从这三点入手,按需禁用缓存/编译器优化。至于线程切换这一点,我们不能禁止,因为这个是解决慢的问题提出的方案呀。
    我们先看按需禁用缓存/编译器优化问题的解决方案:

    给出了volatile,final,synchronized,Happens-Before规则这几个概念。

    • volatile
      就是禁用缓存的意思,只能从内存读取。
      那这样就可以解决可见性问题啦
    • final
      就不用说啦,不可修改,但是我有一个疑惑,对于基本类型是不可修改,但如果是一个实体,那么还是可以set去修改里面的值,那应该还是有可见性问题。
    • Happens-Before原则
      这个是重点,大概有6点规则是属于这里面的。
      先明白这个单词的含义,他规则的核心意思是指:前面的操作对后续的操作是可见的,也就说后面的操作是以前面的操作为前提。这点很重要!!!
      例子:
    // 以下代码来源于【参考 1】
    class VolatileExample {
      int x = 0;
      volatile boolean v = false;
      public void writer() {
        x = 42;
        v = true;
      }
      public void reader() {
        if (v == true) {
          // 这里 x 会是多少呢?
        }
      }
    }
    
    

    答案是x=42,就是根据Happens-Before原则。

    1. 程序顺序性原则
    2. volatile 的写操作 Happens-Before后面的读操作
    3. 传递性
      以上三条就可以的得出 x=42
    4. 管程中的锁
      大意是说一个锁的解锁会Happens-Before后面对这个锁的加锁
    5. 线程start()
      大意是说主线程里开启线程B,线程B能看到主线程在启动子线程B之前的操作
    6. 线程join()
      大意是说主线程能看到子线程的操作
    • 原子性问题的解决方案
      有一个经典的例子,在32位cpu上对long写操作,long是64位,写操作会被分为两步,高32位和低32位。如果被两个线程去写,就会有问题啦。

    所以呢,我们要保证同一时刻只有一个线程被执行,称之为互斥。这个是解决问题的思路~

    写了好久,先去吃块饼干🍪,休息会~

    相关文章

      网友评论

          本文标题:Java并发学习笔记

          本文链接:https://www.haomeiwen.com/subject/vivqlctx.html