精华内容
下载资源
问答
  • 可见是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。 我们在再有人问你Java内存模型是什么,就把这篇文章发给他。中分析过:Java内存模型规定了所有的变量都存储...

    作者:宁cn
    链接:https://www.zhihu.com/question/30562300/answer/666111140
    来源:知乎
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
     

    synchronized与可见性

    可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

    我们在再有人问你Java内存模型是什么,就把这篇文章发给他。中分析过:Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。所以,就可能出现线程1改了某个变量的值,但是线程2不可见的情况。

    前面我们介绍过,被synchronized修饰的代码,在开始执行时会加锁,执行完成后会进行解锁。而为了保证可见性,有一条规则是这样的:对一个变量解锁之前,必须先把此变量同步回主存中。这样解锁后,后续线程就可以访问到被修改后的值。

    所以,synchronized关键字锁住的对象,其值是具有可见性的。

    synchronized与有序性

    有序性即程序执行的顺序按照代码的先后顺序执行。

    我们在再有人问你Java内存模型是什么,就把这篇文章发给他。中分析过:除了引入了时间片以外,由于处理器优化和指令重排等,CPU还可能对输入代码进行乱序执行,比如load->add->save 有可能被优化成load->save->add 。这就是可能存在有序性问题。

    这里需要注意的是,synchronized是无法禁止指令重排和处理器优化的。也就是说,synchronized无法避免上述提到的问题。

    那么,为什么还说synchronized也提供了有序性保证呢?

    这就要再把有序性的概念扩展一下了。Java程序中天然的有序性可以总结为一句话:如果在本线程内观察,所有操作都是天然有序的。如果在一个线程中观察另一个线程,所有操作都是无序的。

    以上这句话也是《深入理解Java虚拟机》中的原句,但是怎么理解呢?周志明并没有详细的解释。这里我简单扩展一下,这其实和as-if-serial语义有关。

    as-if-serial语义的意思指:不管怎么重排序(编译器和处理器为了提高并行度),单线程程序的执行结果都不能被改变。编译器和处理器无论如何优化,都必须遵守as-if-serial语义。

    这里不对as-if-serial语义详细展开了,简单说就是,as-if-serial语义保证了单线程中,指令重排是有一定的限制的,而只要编译器和处理器都遵守了这个语义,那么就可以认为单线程程序是按照顺序执行的。当然,实际上还是有重排的,只不过我们无须关心这种重排的干扰。

    所以呢,由于synchronized修饰的代码,同一时间只能被同一线程访问。那么也就是单线程执行的。所以,可以保证其有序性。

    展开全文
  • Java并发(五)有序性

    2021-01-24 08:59:42
    有序性定义: 指的是在代码顺序结构中,我们可以直观的指定代码的执行顺序, 即从上到下按序执行。但编译器和CPU处理器会根据自己的决策,对代码的执行顺序进行重新排序。优化指令的执行顺序,提升程序的性能和执行...

    人生有涯,学海无涯

    有序性定义: 指的是在代码顺序结构中,我们可以直观的指定代码的执行顺序, 即从上到下按序执行。但编译器和CPU处理器会根据自己的决策,对代码的执行顺序进行重新排序。优化指令的执行顺序,提升程序的性能和执行速度,使语句执行顺序发生改变,出现重排序,但最终结果看起来没什么变化(单核)。

    有序性问题 指的是在多线程环境下(多核),由于执行语句重排序后,重排序的这一部分没有一起执行完,就切换到了其它线程,导致的结果与预期不符的问题。这就是编译器的编译优化给并发编程带来的程序有序性问题

    用图示就是:

    img

    一、导致有序性的原因:

    如果一个线程写入值到字段 a,然后写入值到字段 b ,而且b的值不依赖于 a 的值,那么,处理器就能够自由的调整它们的执行顺序,而且缓冲区能够在 a 之前刷新b的值到主内存。此时就可能会出现有序性问题。

    例子:

    public class OrderlyDemo {
    
        static int value = 1;
        
        private static boolean flag = false;
    
        public static void main(String[] args) throws InterruptedException {
            for (int i = 0; i < 500; i++) {
                value = 1;
                flag = false;
                Thread thread1 = new DisplayThread();
                Thread thread2 = new CountThread();
                thread1.start();
                thread2.start();
                System.out.println("=========================================================");
                Thread.sleep(4000);
            }
        }
    
        static class DisplayThread extends Thread {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " DisplayThread begin, time:" + LocalDateTime.now());
                value = 1024;
                System.out.println(Thread.currentThread().getName() + " change flag, time:" + LocalDateTime.now());
                flag = true;
                System.out.println(Thread.currentThread().getName() + " DisplayThread end, time:" + LocalDateTime.now());
            }
        }
    
        static class CountThread extends Thread {
            @Override
            public void run() {
                if (flag) {
                    System.out.println(Thread.currentThread().getName() + " value的值是:" + value + ", time:" + LocalDateTime.now());
                    System.out.println(Thread.currentThread().getName() + " CountThread flag is true,  time:" + LocalDateTime.now());
                } else {
                    System.out.println(Thread.currentThread().getName() + " value的值是:" + value + ", time:" + LocalDateTime.now());
                    System.out.println(Thread.currentThread().getName() + " CountThread flag is false, time:" + LocalDateTime.now());
                }
            }
        }
    }
    

    运行结果:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rZT21UpK-1611449877645)(D:\新建文件夹 (3)]\image-20210124013939751.png)

    从打印的可以看出:在 DisplayThread 线程执行的时候肯定是发生了重排序,导致先为 flag 赋值,然后切换到 CountThread 线程,这才出现了打印的 value 值是1,falg 值是 true 的情况,再为 value 赋值;不过出现这种情况的原因就是这两个赋值语句之间没有联系,所以编译器在进行代码编译的时候就可能进行指令重排序。

    用图示,则为:

    img

    二、如何解决有序性

    2.1、volatile

    volatile 的底层是使用内存屏障来保证有序性的(让一个Cpu缓存中的状态(变量)对其他Cpu缓存可见的一种技术)。

    volatile 变量有条规则是指对一个 volatile 变量的写操作, Happens-Before 于后续对这个 volatile 变量的读操作。并且这个规则具有传递性,也就是说:

    使用 volatile 修饰flag就可以避免重排序和内存可见性问题。写 volatile 变量时,可以确保 volatile 写之前的操作不会被编译器重排序到 volatile 写之后。读

    volatile 变量时,可以确保 volatile 读之后的操作不会被编译器重排序到 volatile 读之前。

    img

    此时,我们定义变量 flag 时使用 volatile 关键字修饰,如:

        private static volatile boolean flag = false; 
    

    此时,变量的含义是这样子的:

    img

    也就是说,只要读取到 flag=true; 就能读取到 value=1024;否则就是读取到 flag=false;value=1 的还没被修改过的初始状态;

    在这里插入图片描述

    但也有可能会出现线程切换带来的原子性问题,就是读取到 flag=false;value=1024 的情况;看过上一篇讲述原子性的文章的小伙伴,可能就立马明白了,这是线程切换导致的。

    在这里插入图片描述

    2.2、加锁

    此处我们直接采用Java语言内置的关键字 synchronized,为可能会重排序的部分加锁,让其在宏观上或者说执行结果上看起来没有发生重排序。

    代码修改也很简单,只需用 synchronized 关键字修饰run方法即可,代码如下:

    public synchronized void run() {
        value = 1024;
        flag = true;
    }
    

    最后

    最后,简单总结下几种解决方案之间的区别:

    特性Atomic变量volatile关键字Lock接口synchronized关键字
    原子性可以保障无法保障可以保障可以保障
    可见性可以保障可以保障可以保障可以保障
    有序性无法保障一定程度保障可以保障可以保障
    展开全文
  • 目录 0.简介 1.可见性 1.1 不可见性 ...3.3 synchronized有序性 4.程序员学习方法心得 5.总结 0.简介 前一篇文章《Synchronized用法原理和锁优化升级过程》从面试角度详细分析了synchronized关键

    目录

    0.简介

    1.可见性

    1.1 不可见性

    1.2 volatile可见性

    1.3 synchronized可见性

    2.原子性

    2.1 原子性

    2.2 volatile 非原子性

    2.3 synchronized 原子性

    3.有序性

    3.1 有序性

    3.2 volatile有序性

    3.3 synchronized有序性

    4.程序员学习方法心得

    5.总结


    0.简介


    前一篇文章《Synchronized用法原理和锁优化升级过程》从面试角度详细分析了synchronized关键字原理,本篇文章主要围绕volatile关键字用代码分析下可见性,原子性,有序性,synchronized也辅助证明一下,来加深对锁的理解。

    image.png

     

    1.可见性


    1.1 不可见性

    A线程操作共享变量后,该共享变量对线程B是不可见的。我们来看下面的代码。

    package com.duyang.thread.basic.volatiletest;
    /**
     * @author :jiaolian
     * @date :Created in 2020-12-22 10:10
     * @description:不可见性测试
     * @modified By:
     * 公众号:叫练
     */
    public class VolatileTest {
    
        private static boolean flag = true;
    
        public static void main(String[] args) throws InterruptedException {
            Thread threadA = new Thread(() -> {
                while (flag){
                    //注意在这里不能有输出
                };
                System.out.println("threadA over");
            });
            threadA.start();
            //休眠100毫秒,让线程A先执行
            Thread.sleep(100);
            //主线程设置共享变量flag等于false
            flag = false;
        }
    }

    上述代码中,在主线程中启动了线程A,主线程休眠100毫秒,目的是让线程A先执行,主线程最后设置共享变量flag等于false,控制台没有输出结果,程序死循环没有结束不了。如下图所示主线程执行完后flag = false后Java内存模型(JMM),主线程把自己工作内存的flag值设置成false后同步到主内存,此时主内存flag=false,线程A并没有读取到主内存最新的flag值(false),主线程执行完毕,线程A工作内存一直占着cpu时间片不会从主内存更新最新的flag值,线程A看不到主内存最新值,A线程使用的值和主线程使用值不一致,导致程序混乱,这就是线程之间的不可见性,这么说你应该能明白了。线程间的不可见性是该程序死循环的根本原因。

    image.png

    1.2 volatile可见性

    上述案例中,我们用代码证明了线程间的共享变量是不可见的,其实你可以从上图得出结论:只要线程A的工作内存能够感知主内存中共享变量flag的值发生变化就好了,这样就能把最新的值更新到A线程的工作内存了,你只要能想到这里,问题就已经结束了,没错,volatile关键字就实现了这个功能,线程A能感知到主内存共享变量flag发生了变化,于是强制从主内存读取到flag最新值设置到自己工作内存,所以想要VolatileTest代码程序正常结束,用volatile关键字修饰共享变量flag,private volatile static boolean flag = true;就大功告成。volatile底层实现的硬件基础是基于硬件架构和缓存一致性协议。如果想深入下,可以翻看上一篇文章可见性是什么?(通俗易懂)》。一定要试试才会有收获哦!

    image.png

    1.3 synchronized可见性

    synchronized是能保证共享变量可见的。每次获取锁都重新从主内存读取最新的共享变量。

    /**
     * @author :jiaolian
     * @date :Created in 2020-12-22 10:10
     * @description:不可见性测试
     * @modified By:
     * 公众号:叫练
     */
    public class VolatileTest {
    
        private static boolean flag = true;
    
        public static void main(String[] args) throws InterruptedException {
            Thread threadA = new Thread(() -> {
                while (flag){
                    synchronized (VolatileTest.class){
                        
                    }
                };
                System.out.println("threadA over");
            });
            threadA.start();
            //休眠100毫秒,让线程A先执行
            Thread.sleep(100);
            //主线程设置共享变量flag等于false
            flag = false;
        }
    }

    上述代码中,我在线程A的while循环中加了一个同步代码块,synchronized (VolatileTest.class)锁的是VolatileTest类的class。最终程序输出"threadA over",程序结束。可以得出结论:线程A每次加锁前会去读取主内存共享变量flag=false这条最新的数据。由此证明synchronized关键字和volatile有相同的可见性语义。

    image.png

    2.原子性


    2.1 原子性

    原子性是指一个操作要么成功,要么失败,是一个不可分割的整体。

    2.2 volatile 非原子性

    /**
     * @author :jiaolian
     * @date :Created in 2020-12-22 11:22
     * @description:Volatile关键字原子性测试
     * @modified By:
     * 公众号:叫练
     */
    public class VolatileAtomicTest {
    
        private volatile static int count = 0;
    
        public static void main(String[] args) throws InterruptedException {
            Task task = new Task();
            Thread threadA = new Thread(task);
            Thread threadB = new Thread(task);
            threadA.start();
            threadB.start();
            //主线程等待AB执行完毕!
            threadA.join();
            threadB.join();
            System.out.println("累加count="+count);
        }
    
        private static class Task implements Runnable {
            @Override
            public void run() {
                for(int i=0; i<10000; i++) {
                    count++;
                }
            }
        }
    
    }

    上述代码中,在主线程中启动了线程A,B,每个线程将共享变量count值加10000次,线程AB运行完成之后输出count累加值;下图是控制台输出结果,答案不等于20000,证明了volatile修饰的共享变量并不保证原子性。出现这个问题的根本原因的count++,这个操作不是原子操作,在JVM中将count++分成3步操作执行。

    • 读取count值。
    • 将count加1。
    • 写入count值到主内存。

    当多线程操作count++时,就出现了线程安全问题。

    image.png

    2.3 synchronized 原子性

    我们用synchronized关键字来改造上面的代码。

    /**
     * @author :jiaolian
     * @date :Created in 2020-12-22 11:22
     * @description:Volatile关键字原子性测试
     * @modified By:
     * 公众号:叫练
     */
    public class VolatileAtomicTest {
    
        private static int count = 0;
    
        public static void main(String[] args) throws InterruptedException {
            Task task = new Task();
            Thread threadA = new Thread(task);
            Thread threadB = new Thread(task);
            threadA.start();
            threadB.start();
            //主线程等待AB执行完毕!
            threadA.join();
            threadB.join();
            System.out.println("累加count="+count);
        }
    
        private static class Task implements Runnable {
            @Override
            public void run() {
                //this锁住的是Task对象实例,也就是task
                synchronized (this) {
                    for(int i=0; i<10000; i++) {
                        count++;
                    }
                }
            }
        }
    }

    上述代码中,在线程自增的方法中加了synchronized(this)同步代码块,this锁住的是Task对象实例,也就是task对象;线程A,B执行顺序是同步的,所以最终AB线程运行的结果是20000,控制台输出结果如下图所示。

    image.png

    3.有序性


    3.1 有序性

    什么是有序性?我们写的Java程序代码不总是按顺序执行的,都有可能出现程序重排序(指令重排)的情况,这么做的好处就是为了让执行块的程序代码先执行,执行慢的程序放到后面去,提高整体运行效率。画个简单图后举个实际运用案例代码,大家就学到了。

    image.png

    如上图所示,任务1耗时长,任务2耗时短,JIT编译程序后,任务2先执行,再执行任务1,对程序最终运行结果没有影响,但是提高了效率啊(任务2先运行完对结果没有影响,但提高了响应速度)!

    /**
     * @author :jiaolian
     * @date :Created in 2020-12-22 15:09
     * @description:指令重排测试
     * @modified By:
     * 公众号:叫练
     */
    public class CodeOrderTest {
        private static int x,y,a,b=0;
        private static int count = 0;
        public static void main(String[] args) throws InterruptedException {
    
            while (true) {
                //初始化4个变量
                x = 0;
                y = 0;
                a = 0;
                b = 0;
                Thread threadA = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        a = 3;
                        x = b;
                    }
                });
                Thread threadB = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        b = 3;
                        y = a;
                    }
                });
                threadA.start();
                threadB.start();
                threadA.join();
                threadB.join();
                count++;
                if (x == 0 && y==0) {
                    System.out.println("执行次数:"+count);
                    break;
                } else {
                    System.out.println("执行次数:"+count+","+"x:"+x +" y:"+y);
                }
            }
    
        }
    }

    上述代码中,循环启动线程A,B,如果说x,y都等于0时,程序退出。count是程序次数计数器。下图是控制台程序打印部分结果。从图上可以分析出x,y都等于0时,线程A的a = 3; x = b;两行代码做了重排序,线程B中 b = 3;y = a;两行代码也做了重排序。这就是JIT编译器优化代码重排序后的结果。

    image.png

    3.2 volatile有序性

    被volatile修饰的共享变量相当于屏障,屏障的作用是不允许指令随意重排的,有序性主要表现在下面三个方面。

    3.2.1 屏障上面的指令可以重排序。

    /**
     * @author :jiaolian
     * @date :Created in 2020-12-22 15:09
     * @description:指令重排测试
     * @modified By:
     * 公众号:叫练
     */
    public class VolatileCodeOrderTest {
        private static int x,y,a,b=0;
        private static volatile int c = 0;
        private static volatile int d = 0;
        private static int count = 0;
        public static void main(String[] args) throws InterruptedException {
    
            while (true) {
                //初始化4个变量
                x = 0;
                y = 0;
                a = 0;
                b = 0;
                c = 0;
                d = 0;
                Thread threadA = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        a = 3;
                        x = b;
                        c = 4;
                    }
                });
                Thread threadB = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        b = 3;
                        y = a;
                        d = 4;
                    }
                });
                threadA.start();
                threadB.start();
                threadA.join();
                threadB.join();
                count++;
                if (x == 0 && y==0) {
                    System.out.println("执行次数:"+count);
                    break;
                } else {
                    System.out.println("执行次数:"+count+","+"x:"+x +" y:"+y);
                }
            }
    
        }
    }

    上述代码中,循环启动线程A,B,如果说x,y都等于0时,程序退出。共享变量c,d是volatile修饰,相当于内存屏障,count是程序次数计数器。下图是控制台程序打印部分结果。从图上可以分析出x,y都等于0时,线程A的a = 3; x = b;两行代码做了重排序,线程B中 b = 3;y = a;两行代码也做了重排序。证明了屏障上面的指令是可以重排序的。

    image.png

    3.2.2 屏障下面的指令可以重排序。

    image.png

    如上图所示将c,d屏障放到普通变量上面,再次执行代码,依然会有x,y同时等于0的情况,证明了屏障下面的指令是可以重排的。

    3.2.3 屏障上下的指令不可以重排序。

    /**
     * @author :jiaolian
     * @date :Created in 2020-12-22 15:09
     * @description:指令重排测试
     * @modified By:
     * 公众号:叫练
     */
    public class VolatileCodeOrderTest {
        private static int x,y,a,b=0;
        private static volatile int c = 0;
        private static volatile int d = 0;
        private static int count = 0;
        public static void main(String[] args) throws InterruptedException {
    
            while (true) {
                //初始化4个变量
                x = 0;
                y = 0;
                a = 0;
                b = 0;
                c = 0;
                d = 0;
                Thread threadA = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        a = 3;
                        //禁止上下重排
                        c = 4;
                        x = b;
                    }
                });
                Thread threadB = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        b = 3;
                        //禁止上下重排
                        d = 4;
                        y = a;
                    }
                });
                threadA.start();
                threadB.start();
                threadA.join();
                threadB.join();
                count++;
                if (x == 0 && y==0) {
                    System.out.println("执行次数:"+count);
                    break;
                } else {
                    System.out.println("执行次数:"+count+","+"x:"+x +" y:"+y);
                }
            }
    
        }
    }

    如上述代码,将屏障放在中间,会禁止上下指令重排,x,y变量不可能同时为0,该程序会一直陷入死循环,结束不了,证明了屏障上下的代码不可以重排。

    3.3 synchronized有序性

    /**
     * @author :jiaolian
     * @date :Created in 2020-12-22 15:09
     * @description:指令重排测试
     * @modified By:
     * 公众号:叫练
     */
    public class VolatileCodeOrderTest {
        private static int x,y,a,b=0;
        private static int count = 0;
        public static void main(String[] args) throws InterruptedException {
    
            while (true) {
                //初始化4个变量
                x = 0;
                y = 0;
                a = 0;
                b = 0;
                Thread threadA = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        synchronized (VolatileCodeOrderTest.class) {
                            a = 3;
                            x = b;
                        }
                    }
                });
                Thread threadB = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        synchronized (VolatileCodeOrderTest.class) {
                            b = 3;
                            y = a;
                        }
                    }
                });
                threadA.start();
                threadB.start();
                threadA.join();
                threadB.join();
                count++;
                if (x == 0 && y==0) {
                    System.out.println("执行次数:"+count);
                    break;
                } else {
                    System.out.println("执行次数:"+count+","+"x:"+x +" y:"+y);
                }
            }
        }
    }

    上述代码中,x,y也不可能同时等于0,synchronized锁的VolatileCodeOrderTest的class对象,线程A,B是同一把锁,代码是同步执行的,是有先后顺序的,所以synchronized也能保证有序性。值得注意的一点是上述代码synchronized不能用synchronized(this),this表示当前线程也就是threadA或threadB,就不是同一把锁了,如果用this测试会出现x,y同时等于0的情况。

    4.程序员学习方法心得


    大家可以看到我最近几篇文章分析多线程花了不少精力都在谈论可见性,原子性等问题,因为这些特性是理解多线程的基础,在我看来基础又特别重要,所以怎么反复写我认为都不过分,在这之前有很多新手或者有2到3年工作经验的童鞋经常会问我关于Java的学习方法,我给他们的建议就是要扎实基础,别上来就学高级的知识点或者框架,比如ReentrantLock源码,springboot框架,就像你玩游戏,一开始你就玩难度级别比较高的,一旦坡度比较高你就会比较难受吃力更别说对着书本了,这就是真正的从入门到放弃的过程。同时在学习的时候别光思考,觉得这个知识点自己会了就过了,这是不够的需要多写代码,多实践,你在这个过程中再去加深自己对知识的理解与记忆,其实有很多知识你看起来是理解了,但是你没有动手去实践,也没有真正理解,这样只看不做的方法我是不推荐的,本人本科毕业后工作7年,一直从事Java一线的研发工作,中间也带过团队,因为自己曾经也走过很多弯路踏着坑走过来的,对学习程序还是有一定的心得体会,我会在今后的日子里持续整理把一些经验和知识方面的经历分享给大家,希望大家喜欢关注我。我是叫练,叫个口号就开始练!

    总结下来就是两句话:多动手,扎实基础

    image.png

    5.总结


    今天给和大家聊了多线程的3个重要的特性,用代码实现的方式详细阐述了这些名词的含义,如果认真执行了一遍代码应该能看明白,喜欢的请点赞加关注哦。我是叫练【公众号】,边叫边练。

    image.png

    展开全文
  • 从可见性与有序性问题的原因着手导致可见性问题的原因是缓存,导致有序性问题的原因是编译优化,那么解决二者的最直接方法就是禁用缓存和编译优化。但是这样程序的性能将会受到很大程度降低。这里较为合理的方案是按...

    从可见性与有序性问题的原因着手

    导致可见性问题的原因是缓存,导致有序性问题的原因是编译优化,那么解决二者的最直接方法就是禁用缓存和编译优化。但是这样程序的性能将会受到很大程度降低。

    这里较为合理的方案是按需禁用缓存和编译优化。Java内存模型规范了JVM如何提供按需禁用缓存和编译优化的方法。具体包括:volatile、synchronized和final关键字和Happens-Before规则。

    volatile关键字

    “当变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。---《Java并发编程实战》”

    当将一个变量声明为volatile类型,则表明对于这个变量的读写不使用CPU缓存,必须从内存中读取或写入。

    从对volatile的描述可以看出,被volatile修饰的变量不写入缓存,且不参与重排序,这就解决了可见性与有序性的问题,但这是否保证了线程安全呢?答案是否定的,volatile无法保证原子性--引起并发过程中Bug的另一个源头。关于原子性的保证,将在后面进行讨论。

    Happens-Before规则

    "In computer science, the happened-before relation (denoted: → ) is a relation between the result of two events, such that if one event should happen before another event, the result must reflect that, even if those events are in reality executed out of order (usually to optimize program flow)." ---wikipedia

    "In Java specifically, a happens-before relationship is a guarantee that memory written to by statement A is visible to statement B, that is, that statement A completes its write before statement B starts its read." ---wikipedia

    Happens-Before规则所描述的是:先后发生的两个操作,其结果必须也体现操作的先后顺序,即:前一个操作的结果对后续操作是可见的。Happens-Before规则具体有以下六项:

    1.程序的顺序性规则

    如以下代码,按照程序的执行顺序,"x = 12;" Happens-Before 于 "v = true;",对于x的赋值一定发生在对v赋值之前,即对x的操作对后续操作可见。

    class VolatileExample{

    int x = 0;

    volatile boolean v = false;

    public void writer(){

    x = 12;

    v = true;

    }

    public void reader(){

    if(v == true){

    //x=?

    }

    }

    }

    复制代码

    2.volatile变量规则

    规则2代表针对volatile变量的写操作,Happens-Before 于后续对这个变量的读操作。

    3.传递性

    规则3表示:若 A Happens-Before B,且 B Happens-Before C,那么 A Happens-Before C。

    正如上面的示例代码,假设线程A先调用writer()方法,线程B之后调用reader()方法,则:

    1.根据规则1,"x = 12" Happens-Before "v = true";

    2.根据规则2,写变量 "v = true" Happens-Before 于读变量 "v = true";

    3.再根据规则3,"x = 12" Happens-Before 读变量 "v = true"。

    这就意味着,线程A对x的写操作对于线程B对x的读操作是可见的,则线程B读取的x为12。

    4.管程中锁的规则

    规则4是指一个锁的解锁操作 Happens-Before 于后续对这个锁的加锁。

    管程是一种通用的同步原语,在Java中指的就是synchronized,synchronized是Java里对管程的实现。

    结合规则3,获得锁的线程在解锁之前的操作 Happens-Before 解锁操作,加上规则4的内容,则上述操作 Happens-Before 后续的加锁操作,也就是该操作对后面获得锁的线程来说是可见的。

    5.线程start()规则

    规则5指主线程A启动子线程B后,子线程B能够看到A在启动子线程B之前的操作。

    也就是线程A调用线程B的start()方法,那么该start()操作 Happens-Before 于线程B中的任意操作。

    Thread B = new Thread() {

    //主线程调用B.strat()之前所有对共享变量的修改

    //此处皆可见,var为20

    }

    //对共享变量赋值

    var = 20;

    //主线程启动子线程

    B.start();

    复制代码

    6.线程join()规则

    规则6描述线程间的等待关系,当主线程通过join()方法调用子线程,等待子线程完成。子线程中对共享变量的操作对主线程全部可见。即:子线程中的任意操作 Happens-Before 与join()方法的返回。

    总结

    1.volatile关键字通过禁用缓存和指令重排序的方式保证了程序运行中的可见性与有序性;

    2.Happens-Before 本质上是一种描述可见性的规则,意味着若事件A Happens-Before 事件B,则A中的所有操作对B而言都是可见的,无论事件AB是否发生在同一个线程上。如事件A发生在线程1,事件B发生在线程2上,Happens-Before 规则依旧保证线程2上可以看见A事件的发生。

    展开全文
  • 并发编程三大核心问题并发编程:有序性问题,有序性。顾名思义,有序性指的是程序按照代码的先后顺序执行。编译器为了优化性能,有时候会改变程序中语句的先后顺序。例如程序中:“a=6;b=7;” 编译器优化后可能...
  • 消息有序性2. 发送端消息有序性2.1 Kafka如何保证单partition有序?2.2 client消息发送原理接收端消息有序性参考 1. 消息有序性 我们需要从2个方面看待消息有序性 第一,发送端能否保证发送到服务器的消息是有序的...
  • 在上一篇文章中,你可能已经知道了,导致可见性的原因是缓存,导致有序性的原因是编译优化,那解决可见性、有序性最直接的办法就是禁用缓存和编译优化,但是这样问题虽然解决了,我们程序的性能可就堪忧了。...
  • 一方面加了锁之后,只能有一个线程获得到了锁,获得不到锁的线程就要阻塞。所以同一时间只有一个线程执行,相当于单...总结,synchronized可以保证有序性但是不能避免指令重排,在双重检验的单例模式中,必须加volat.
  • Synchronized与原子 原子是指一个操作是不可中断的,要全部执行完成,要不都不执行。 在Java中为了保证原子,提供了两个高级的字节码指令moniterenter和moniterexit。这两个码指令,在Java对应的关键字是...
  • volatile关键字保证线程的可见性且提供了一定的有序性,但是我无法保证原子性。 在JVM底层volatile是采用内存屏障实现的。
  • 1. 未加volatile关键字,由于缓存不一致导致线程间数据交互失败 public class Test { private static int INIT_VAL = 0; private static final Integer MAX_VAL = 5; public static void main(String[] args...
  • volatile有序性的实现原理
  • 缓存导致的可见问题 在多核cpu时代,cpu缓存的同步会导致共享变量的操作结果在多个线程之间不可见(线程的主内存与工作内存同步),进而导致并发问题。 int count=0; 如下图若线程A和线程B同时做count+=1操作,...
  • 原子性、可见性、有序性分别是什么? 原子性 基本数据类型的访问都具备原子性,例外就是 long 和 double,虚拟机将没有被 volatile 修饰的 64 位数据操作划分为两次 32 位操作。 如果应用场景需要更大范围的原子性...
  • } } 那我们首先要明白: 为啥synchronized无法禁止指令重排,但可以保证有序性? 加了锁之后,只能有一个线程获得到了锁,获得不到锁的线程就要阻塞。所以同一时间只有一个线程执行,相当于单线程,而单线程的指令...
  • 有序性(Ordering): 对于一个线程的执行代码而言,我们总是习惯地认为代码是从前往后依次执行的;这么理解不完全正确,因为就一个线程而言,确实会表现成这样。但是。在并发时,程序的执行可能就会出现乱序。 ...
  • Java程序中天然的有序性可以总结为一句话:如果在本线程内观察,所有操作都是天然有序的。如果在一个线程中观察另一个线程,所有操作都是无序的。前半句是指“线程内似表现为串行的语义”,后半句是指“指令重排”...
  • 目录定义原子性AtomicXxxAtomicStampedReference可见性有序性参考资料 定义 首先大家需要思考一下何为线程安全性呢??? 《Java并发编程实战》书中给出定义:当多个线程访问某个类时,不管运行时环境采用何种调度...
  • JMM保证了在多核CPU多线程编程环境下,对共享变量读写的原子性、可见性和有序性。本文就具体来讲讲JMM是如何保证共享变量访问的有序性的。指令重排在说有序性之前,我们必须先来聊下指令重排,因为如果没有指令重拍...
  • 当初有个面试题,说怎么保证数据有序性。记得当初没有经验。然后就用了最笨的方法。 2.笨方法 最笨的方法是,kafka一个分区,FLink一个并行度,Spark一个并行度 这个首先得局限性是 分区数固定 flink并行度固定 ...
  • 使用volatile关键字修饰变量,写变量时,可以确保volatile写之前的操作不会被编译器重排序到volatile写之后,这样就保证了写之前的操作是完整的,这样修改出来的变量值才是正确的,而读变量时,确保读变量后的操作...
  • 一、 如何保证不乱序,也就是保证有序性 1、 硬件内存屏障 ​ 注意:这是inter X86 1.1 sfence ​ store fence 在sfence指令前面的写操作必须在sfence指令后边的写操作前完成 如图:如果没有sfence ,是不能保证操作1...
  • 什么是有序性? 即程序执行的顺序按照代码的先后顺序执行。 编译器和处理器为了提高执行的效率性能,会对执行进行重排序,指令重排序对单线程应用没有影响,但是在多线程环境下可能会出现问题。 有序性问题 线程A ...
  • 消息的顺序怎么保证? 将需要有序的消息,生产到同一个partition,而消费着也消费这一个partition
  • 要想实现消息有序,需要从 Producer 和 Consumer 两方面来考虑。 首先,Producer 生产消息的时候就必须要有序。 然后,Consumer 消费的时候,也要按顺序来,不能乱。 Producer 有序 像 RabbitMQ 这类普通的消息系统...
  • TreeMap,HashMap,LinkedHashMap 接下来介绍set集合的有序性和唯一性 Set HashSet:无序,唯一 Set LinkedHashSet:有序,唯一 Set TreeSet:有序,唯一 Set集合特点是无序,唯一 List集合的优缺点 List ArrayList:...
  • 先告诉面试官你知道什么是有序性问题,也知道是什么原因导致的有序性问题我们也知道,最好的解决有序性问题的办法,就是禁止处理器优化和指令重排,就像volatile中使用内存屏障一样。表明你知道啥是指令重排,也知道...
  • volatile关键字介绍一:线程安全的三大特性1、原子性2、可见性3、有序性二:volatile关键字 一:线程安全的三大特性 1、原子性 原子性是指操作是不可分的。其表现在于对于共享变量的某些操作,应该是不可分的,必须...
  • 原子性:是指一个操作是不可中断的。即使是多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。 比如,对于一个静态全局变量int i,两个线程同时对它赋值,线程A...有序性:在并发时,程序的执行可能会
  • 之前的项目中由于用到了Kafka,所以在前一阵的实习面试中被问到了如何保证Kafka消息的有序性,所以本文对于Kafka这三个常被问到的问题进行总结归纳。 可靠性 对于消息队列而言,如果不能保证消息的可靠性可能会引起...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 407,106
精华内容 162,842
关键字:

有序性

友情链接: USB-Driver(USB2COM).zip