• synchronized 与volatile


    关键字synchronized的主要作用是保证同一时刻,只有一个线程可以执行某一个方法,或是某一个代码块,synchronized可以修饰方法及代码块。随着JDK的版本升级,synchronized关键字在执行效率上得到很大提升。它包含三个特征。

    1)可见性:synchronized具有可见性。

    2)原子性:使用synchronized实现了同步,同步实现了原子性,保证被同步的代码段在同一时间只有一个线程在执行。

    3)禁止代码重排序:synchronized禁止代码重排序。

    关键字volatile的主要作用是让其他线程可以看到最新的值,volatile只能修饰变量。它包含三个特征:

    1)可见性:B线程能马上看到A线程更改的数据。

    2)原子性:在32位系统中,针对未使用volatile声明的long或double数据类型没有实现写原子性,如果想实现,则声明变量时添加volatile,而在64位系统中,原子性取决于具

    2.3.3 禁止代码重排序的测试

    使用关键字volatile可以禁止代码重排序。

    在Java程序运行时,JIT(Just-In-Time Compiler,即时编译器)可以动态地改变程序代码运行的顺序,例如,有如下代码:


    A代码-重耗时
    B代码-轻耗时
    C代码-重耗时
    D代码-轻耗时
    

    在多线程的环境中,JIT有可能进行代码重排,重排序后的代码顺序有可能如下:


    B代码-轻耗时
    D代码-轻耗时
    A代码-重耗时
    C代码-重耗时
    

    这样做的主要原因是CPU流水线是同时执行这4个指令的,那么轻耗时的代码在很大程度上先执行完,以让出CPU流水线资源给其他指令,所以代码重排序是为了追求更高的程序运行效率。

    重排序发生在没有依赖关系时,例如,对于上面的A、B、C、D代码,B、C、D代码不依赖A代码的结果,C、D代码不依赖A、B代码的结果,D代码不依赖A、B、C代码的结果,这种情况下就会发生重排序,如果代码之间有依赖关系,则代码不会重排序。

    使用关键字volatile可以禁止代码重排序,例如,有如下代码:


    A变量的操作
    B变量的操作
    volatile Z变量的操作
    C变量的操作
    D变量的操作
    

    那么会有4种情况发生:

    1)A、B可以重排序。

    2)C、D可以重排序。

    3)A、B不可以重排到Z的后面。

    4)C、D不可以重排到Z的前面。

    换言之,变量Z是一个“屏障”,Z变量之前或之后的代码不可以跨越Z变量,这就是屏障的作用,关键字synchronized具有同样的特性。

    1.实现代码重排序的测试

    虽然代码重排序后能提高程序的运行效率,但在有逻辑性的程序中就容易出现一些错误,下面通过示例验证一下。

    创建测试用的项目reorderTest。

    创建类代码如下:


    package test;
    
    public class Test1 {
        private static long x = 0;
        private static long y = 0;
        private static long a = 0;
        private static long b = 0;
        private static long c = 0;
        private static long d = 0;
        private static long e = 0;
        private static long f = 0;
    
        private static long count = 0;
    
        public static void main(String[] args) throws InterruptedException {
            for (;;) {
                x = 0;
                y = 0;
                a = 0;
                b = 0;
                c = 0;
                d = 0;
                e = 0;
                f = 0;
                count++;
                Thread t1 = new Thread(new Runnable() {
                    public void run() {
                        a = 1;
                        c = 101;
                        d = 102;
                        x = b;
                    }
                });
    
                Thread t2 = new Thread(new Runnable() {
                    public void run() {
                        b = 1;
                        e = 201;
                        f = 202;
                        y = a;
                    }
                });
                t1.start();
                t2.start();
                t1.join();
                t2.join();
                String showString = "count=" + count + " " + x + "," + y + "";
                if (x == 0 && y == 0) {
                    System.err.println(showString);
                    break;
                } else {
                    System.out.println(showString);
                }
            }
        }
    }
    

    程序运行后控制台输出最后的部分结果如下:


    count=119630 0,1
    count=119631 0,1
    count=119632 0,1
    count=119633 0,1
    count=119634 0,1
    count=119635 0,0
    

    程序输出x和y都是0,这时就出现了代码重排序,重排后的顺序如下:


    x = b;
    a = 1;
    c = 101;
    d = 102;
    


    y = a;
    b = 1;
    e = 201;
    f = 202;
    

    结果是x和y均为0。

    2.关键字volatile之前的代码可以重排

    下面通过示例验证volatile“之前”的代码可以出现代码重排的效果,重排后的顺序如下:


    x = b;
    a = 1;
    c = 101;
    d = 102;
    


    y = a;
    b = 1;
    e = 201;
    f = 202;
    

    结果是x和y均为0。

    程序代码如下:


    package test;
    
    public class Test2 {
        private static long x = 0;
        private static long y = 0;
        private static long a = 0;
        private static long b = 0;
        private static long c = 0;
        volatile private static long d = 0;
        private static long e = 0;
        volatile private static long f = 0;
    
        private static long count = 0;
    
        public static void main(String[] args) throws InterruptedException {
            for (;;) {
                x = 0;
                y = 0;
                a = 0;
                b = 0;
                c = 0;
                d = 0;
                e = 0;
                f = 0;
                count++;
                Thread t1 = new Thread(new Runnable() {
                    public void run() {
                        a = 1;
                        c = 101;
                        x = b;
                        d = 102;
                    }
                });
    
                Thread t2 = new Thread(new Runnable() {
                    public void run() {
                        b = 1;
                        e = 201;
                        y = a;
                        f = 202;
                    }
                });
                t1.start();
                t2.start();
                t1.join();
                t2.join();
                String showString = "count=" + count + " " + x + "," + y + "";
                if (x == 0 && y == 0) {
                    System.err.println(showString);
                    break;
                } else {
                    System.out.println(showString);
                }
            }
        }
    }
    

    程序运行结果如下:


    count=33853 0,1
    count=33854 0,1
    count=33855 0,1
    count=33856 0,1
    count=33857 0,1
    count=33858 0,0
    

    关键字volatile之前的代码发生了重排。

    3.关键字volatile之后的代码可以重排

    下面通过示例验证volatile之后的代码可以出现代码重排的效果,重排后的顺序如下:


    c = 101;
    x = b;
    a = 1;
    d = 102;
    


    e = 201;
    y = a;
    b = 1;
    f = 202;
    

    结果是x和y均为0。

    程序代码如下:


    package test;
    
    public class Test3 {
        private static long x = 0;
        private static long y = 0;
        private static long a = 0;
        private static long b = 0;
        volatile private static long c = 0;
        private static long d = 0;
        volatile private static long e = 0;
        private static long f = 0;
    
        private static long count = 0;
    
        public static void main(String[] args) throws InterruptedException {
            for (;;) {
                x = 0;
                y = 0;
                a = 0;
                b = 0;
                c = 0;
                d = 0;
                e = 0;
                f = 0;
                count++;
                Thread t1 = new Thread(new Runnable() {
                    public void run() {
                        c = 101;
                        a = 1;
                        d = 102;
                        x = b;
                    }
                });
    
                Thread t2 = new Thread(new Runnable() {
                    public void run() {
                        e = 201;
                        b = 1;
                        f = 202;
                        y = a;
                    }
                });
                t1.start();
                t2.start();
                t1.join();
                t2.join();
                String showString = "count=" + count + " " + x + "," + y + "";
                if (x == 0 && y == 0) {
                    System.err.println(showString);
                    break;
                } else {
                    System.out.println(showString);
                }
            }
        }
    }
    

    程序运行结果如下:


    count=20898 0,1
    count=20899 0,1
    count=20900 0,1
    count=20901 0,1
    count=20902 0,0
    

    关键字volatile之后的代码发生了重排。

    4.关键字volatile之前的代码不可以重排到volatile之后

    下面通过示例验证volatile之前的代码不可以重排到volatile之后,所以x和y同时不为0的情况不会发生。

    程序代码如下:


    package test;
    
    public class Test4 {
        private static long x = 0;
        private static long y = 0;
        private static long a = 0;
        private static long b = 0;
        volatile private static long c = 0;
        volatile private static long d = 0;
    
        private static long count = 0;
    
        public static void main(String[] args) throws InterruptedException {
            for (;;) {
                x = 0;
                y = 0;
                a = 0;
                b = 0;
                c = 0;
                d = 0;
                count++;
                Thread t1 = new Thread(new Runnable() {
                    public void run() {
                        x = b;
                        c = 101;
                        a = 1;
                    }
                });
    
                Thread t2 = new Thread(new Runnable() {
                    public void run() {
                        y = a;
                        d = 201;
                        b = 1;
                    }
                });
                t1.start();
                t2.start();
                t1.join();
                t2.join();
                String showString = "count=" + count + " " + x + "," + y + "";
                if (x != 0 && y != 0) {
                    System.err.println(showString);
                    break;
                } else {
                    System.out.println(showString);
                }
            }
        }
    }
    

    程序运行后,x和y同时不为0的情况不会发生,所以关键字volatile之前的代码不可以重排到volatile之后。

    5.关键字volatile之后的代码不可以重排到volatile之前

    下面通过示例验证volatile之后的代码不可以重排到volatile之前,所以x和y的值永远不可能同时为0。

    程序代码如下:


    package test;
    
    public class Test5 {
        private static long x = 0;
        private static long y = 0;
        private static long a = 0;
        private static long b = 0;
        volatile private static long c = 0;
        volatile private static long d = 0;
    
        private static long count = 0;
    
        public static void main(String[] args) throws InterruptedException {
            for (;;) {
                x = 0;
                y = 0;
                a = 0;
                b = 0;
                c = 0;
                d = 0;
                count++;
                Thread t1 = new Thread(new Runnable() {
                    public void run() {
                        a = 1;
                        c = 101;
                        x = b;
                    }
                });
    
                Thread t2 = new Thread(new Runnable() {
                    public void run() {
                        b = 1;
                        d = 201;
                        y = a;
                    }
                });
                t1.start();
                t2.start();
                t1.join();
                t2.join();
                String showString = "count=" + count + " " + x + "," + y + "";
                if (x == 0 && y == 0) {
                    System.err.println(showString);
                    break;
                } else {
                    System.out.println(showString);
                }
            }
        }
    }
    

    程序运行后,x和y的值永远不可能同时为0,所以关键字volatile之后的代码不可以重排到volatile之前。

    6.关键字synchronized之前的代码不可以重排到synchronized之后

    下面通过示例验证synchronized之前的代码不可以重排到synchronized之后,所以x和y同时不为0的情况不会发生。

    程序代码如下:


    package test;
    
    public class Test6 {
        private static long x = 0;
        private static long y = 0;
        private static long a = 0;
        private static long b = 0;
    
        private static long count = 0;
    
        public static void main(String[] args) throws InterruptedException {
            for (;;) {
                x = 0;
                y = 0;
                a = 0;
                b = 0;
                count++;
                Thread t1 = new Thread(new Runnable() {
                    public void run() {
                        x = b;
                        synchronized (this) {
                        }
                        a = 1;
                    }
                });
    
                Thread t2 = new Thread(new Runnable() {
                    public void run() {
                        y = a;
                        synchronized (this) {
                        }
                        b = 1;
                    }
                });
                t1.start();
                t2.start();
                t1.join();
                t2.join();
                String showString = "count=" + count + " " + x + "," + y + "";
                if (x != 0 && y != 0) {
                    System.err.println(showString);
                    break;
                } else {
                    System.out.println(showString);
                }
            }
        }
    }
    

    程序运行后,x和y同时不为0的情况不会发生,所以关键字synchronized之前的代码不可以重排到synchronized之后。

    7.关键字synchronized之后的代码不可以重排到synchronized之前

    下面通过示例验证synchronized之后的代码不可以重排到synchronized之前,所以x和y的值永远不可能同时为0。

    程序代码如下:


    package test;
    
    public class Test7 {
        private static long x = 0;
        private static long y = 0;
        private static long a = 0;
        private static long b = 0;
    
        private static long count = 0;
    
        public static void main(String[] args) throws InterruptedException {
            for (;;) {
                x = 0;
                y = 0;
                a = 0;
                b = 0;
                count++;
                Thread t1 = new Thread(new Runnable() {
                    public void run() {
                        a = 1;
                        synchronized (this) {
                        }
                        x = b;
                    }
                });
    
                Thread t2 = new Thread(new Runnable() {
                    public void run() {
                        b = 1;
                        synchronized (this) {
                        }
                        y = a;
                    }
                });
                t1.start();
                t2.start();
                t1.join();
                t2.join();
                String showString = "count=" + count + " " + x + "," + y + "";
                if (x == 0 && y == 0) {
                    System.err.println(showString);
                    break;
                } else {
                    System.out.println(showString);
                }
            }
        }
    }
    

    程序运行后,x和y的值永远不可能同时为0,所以关键字synchronized之后的代码不可以重排到synchronized之前。

    8.总结

    关键字synchronized的主要作用是保证同一时刻,只有一个线程可以执行某一个方法,或是某一个代码块,synchronized可以修饰方法及代码块。随着JDK的版本升级,synchronized关键字在执行效率上得到很大提升。它包含三个特征。

    1)可见性:synchronized具有可见性。

    2)原子性:使用synchronized实现了同步,同步实现了原子性,保证被同步的代码段在同一时间只有一个线程在执行。

    3)禁止代码重排序:synchronized禁止代码重排序。

    关键字volatile的主要作用是让其他线程可以看到最新的值,volatile只能修饰变量。它包含三个特征:

    1)可见性:B线程能马上看到A线程更改的数据。

    2)原子性:在32位系统中,针对未使用volatile声明的long或double数据类型没有实现写原子性,如果想实现,则声明变量时添加volatile,而在64位系统中,原子性取决于具体的实现,在X86架构64位JDK版本中,写double或long是原子的。另外,针对用volatile声明的int i变量进行i++操作时是非原子的。

    3)禁止代码重排序。

    学习多线程与并发,要着重“外炼互斥,内修可见,内功有序”,这是掌握多线程、学习多线程和并发技术的重要知识点。

    关键字volatile和synchronized的使用场景总结如下:

    1)当想实现一个变量的值被更改时,让其他线程能取到最新的值时,就要对变量使用volatile。

    2)当多个线程对同一个对象中的同一个实例变量进行操作时,为了避免出现非线程安全问题,就要使用synchronized。

  • 相关阅读:
    一种在【微服务体系】下的【参数配置神器】
    阅读源代码之“那是我的青春”
    我谈防御式编程
    博客开篇
    redis-sentinel-cluster-codis
    redis-复制
    redis-持久化
    redis-事件
    圆角矩形精度条
    小程序---canvas画图,生成分享图片,画图文字换行
  • 原文地址:https://www.cnblogs.com/spilzer/p/16085569.html
Copyright © 2020-2023  润新知