美文网首页
waitset,volatile-多线程第二阶段课程

waitset,volatile-多线程第二阶段课程

作者: jiahzhon | 来源:发表于2020-10-03 23:10 被阅读0次

    waitset

    • 所有的对象都会有一个waitset,用来存放调用了该对象wait方法之后进入block状态的线程。
    • 线程被notify之后,不一定立即得到执行。
    • 线程从wait set中被唤醒的顺序不一定是FIFO.
    • 线程被唤醒后,必须重新获取锁。
      附加:要notify别人的线程也要获取这个锁。

    volatile例子

    • 例子中并没有使用锁,只对INIT_VALUE使用了volatile
    public class VolatileTest {
    
        private static volatile int INIT_VALUE = 0;
    
        private final static int MAX_LIMIT = 9;
    
        public static void main(String[] args) {
    
            new Thread("Reader"){
                @Override
                public void run() {
                    int localValue = INIT_VALUE;
                    while ( localValue < MAX_LIMIT) {
                        if (localValue != INIT_VALUE) {
                            System.out.printf("The value updated to [%d]\n",INIT_VALUE);
                            localValue = INIT_VALUE;
                        }
                    }
                }
            }.start();
    
            new Thread("Updater"){
                @Override
                public void run() {
                    int localValue = INIT_VALUE;
                    while ( localValue < MAX_LIMIT) {
                        System.out.printf("Update the value to [%d]\n", ++localValue);
                        INIT_VALUE = localValue;
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
        }
    }
    
    • 效果


      Snipaste_2020-10-03_23-42-59.png
    • 如果去掉volatile,读线程永远无法感知到


      Snipaste_2020-10-03_23-44-34.png
    • 出现这种现象的原因,在去掉volatile的时候,jvm认为reader线程只有读操作,只需要从CPU缓存中读取就行了。而缓存不同步,所以一直没有感知到新值。(纯读操作才会有这个问题)(如果大家都直接从主存读写,当然不会出现这种问题)(结合下图理解)

    • CPU缓存不同步问题

    Snipaste_2020-10-04_00-17-35.png
    • CPU高速缓存不同步两种解决方案

    • 给数据总线加锁
      • 总线( 数据总线、地址总线、控制总线)
    • CPU高速缓存一致性协议(MESI-intel发明的)
      • 当CPU写入数据的时候,如果发现该变量被共享(也就是说,在其他cpu中也存在该变量的副本),会发出一个信号,通知其他CPU该变量的缓存无效
      • 上面的步骤执行完毕后,当其他事CPU访问该变量时,重新从主内存获取。


        Snipaste_2020-10-04_00-52-51.png

    原子性,可见性,有序性在java中的保证(在多线程中谈才有意义)

    1. 原子性
    • 对基本数据类型的变量读取和赋值都保证了原子性,要么都成功,要么都失败,这些操作不可被中断。
      • 例子 i = 10 (CPU cache = 10, memory = 10)
      • a = 10;满足原子性
      • b = a; 不满足, 1. 读a, 2. 赋值给b
      • c++; 不满足,1. 读c ,2. c+1 3赋值给c
      • c = c+1 同上
    1. 可见性
    • 使用volatile关键字保证可见性
    1. 有序性(java的重排序保证最后结果一致就可以了)
    • happens-before原则
      • 代码的执行顺序,编写在前面的发生在编写在后面的
      • unlock必须发生在lock之后
      • volatile修饰的变量,对一个变量的写操作先于对该变量的读操作
      • 传递规则,操作A先于B,B先于C,那么A肯定先于C
      • 线程的启动规则,start方法肯定先于线程run
      • 线程中断规则,interrupt这个动作,必须发生在捕获该动作之前
      • 对象销毁规则,初始化必须发生在finalize之前
      • 线程终结规则,所有的操作都发生在线程死亡之前

    volatile

    • 保证重排序的时候不会把后面的指令放到屏障的前面,也不会把前面的放到后面
    • 强制对缓存的修改操作立刻写入主存
    • 如果是写操作,他会导致其他CPU中的缓存失效
    • 比较上边没加volatile的纯读操作线程出现的问题:如果大家都是只操作自己cache,那么下边的代码应该都是各自连续加自己的50(实际效果却是两条数据断断续续一起完成加到50,中间还带重复的)
    public class VolatileTest2 {
    
        private static int INIT_VALUE = 0;
    
        private final static  int MAX_LIMIT = 50;
    
        public static void main(String[] args) {
            new Thread("adder-1"){
                @Override
                public void run() {
                    while (INIT_VALUE < MAX_LIMIT) {
                        System.out.println("T1 ->" + (++INIT_VALUE));
                    }
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }.start();
    
            new Thread("adder-2"){
                @Override
                public void run() {
                    while (INIT_VALUE < MAX_LIMIT) {
                        System.out.println("T2 ->" + (++INIT_VALUE));
                    }
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }.start();
        }
    }
    
    • 数据断断续续一起完成加到50的原因是写操作是会将CPU缓存刷回主线程,再从主线程到缓存获取,有写就不会被jvm默认优化。
    • 这里给INIT_VALUE加volatile并不能保证上面就是线程安全的,偶尔会发生重复(原因:没有保证原子性。先获取INIT_VALUE,++INIT_VALUE)获取INIT_VALUE是有原子性的,但获取完后释放cpu执行权被别的线程一气呵成完成步骤,造成数据重复。解决办法:用锁。
    • 使用场景:


      Snipaste_2020-10-04_02-41-21.png

      第二点就是单例模式doublecheck中实例加volatile的原因,加了后确保构造函数执行完毕。

    相关文章

      网友评论

          本文标题:waitset,volatile-多线程第二阶段课程

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