美文网首页Java并发编程
Java的volatile与synchronized关键字使用对

Java的volatile与synchronized关键字使用对

作者: Chermack | 来源:发表于2021-02-13 23:05 被阅读0次

    异同

    volatile:重点在于告诉JVM被标记变量在线程的私有工作内存中的值是不确定的,每次都需要从主存中读取。
    synchronized:对某一对象上锁,被保护的代码块无法并发执行。

    二者都使用了内存屏障,保证“读之前”或“写之后”所有的CPU操作都同步刷新到主存中。

    实验

    该测试在windows10,JDK1.8.0_271下执行:

    volatile的错误用法

    首先测试 volatile标记的整型变量在两个线程下做累加操作:

    public class Test {
        volatile static long a = 0;
    
        public static void main(String[] args) {
            Runnable r = () -> {
                for (int i = 0; i < 100_000_000; i++) {
                    a++;
                }
            };
            Thread t1 = new Thread(r);
            Thread t2 = new Thread(r);
    
            long start = System.currentTimeMillis();
            t1.start();
            t2.start();
            try {
                t1.join();
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            long end = System.currentTimeMillis();
            System.out.println(a);
            System.out.println(end - start);
        }
    }
    
    volatile标记变量测试结果

    因为volatile只能保证原子操作的原子性,a++实际上为复合操作,包括读取,相加,写入三部分,因此并没有得到正确的预期结果。两个线程交替进行a++操作,进行了大量的线程内存和主存的同步操作和数据拷贝,因此花费时间很长。

    synchronized的正确用法

    使用synchronized对当前类对象上锁测试累加结果:

    public class Test {
        static long a = 0;
    
        public static void main(String[] args) {
            Runnable r = () -> {
                synchronized (Test.class) {
                    for (int i = 0; i < 100_000_000; i++) {
                        a++;
                    }
                }
            };
            Thread t1 = new Thread(r);
            Thread t2 = new Thread(r);
    
            long start = System.currentTimeMillis();
            t1.start();
            t2.start();
            try {
                t1.join();
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            long end = System.currentTimeMillis();
            System.out.println(a);
            System.out.println(end - start);
        }
    }
    
    
    synchronized保护代码块测试结果

    可见synchronized获取了正确的实验结果且时间明显变短(因为同步操作只在synchronized加锁前后进行,且同一线程可能多次连续加锁成功,因此数据在线程工作内存和主存之间的拷贝次数明显减少,因而花费时间较短)。

    volatile的正确用法

    import java.util.concurrent.TimeUnit;
    
    public class Test {
    //    volatile static boolean a = true;
        static boolean a = true;
    
        public static void main(String[] args) {
            Thread t1 = new Thread(() -> {
                while (a) {}
            });
            Thread t2 = new Thread(() -> {
                a = false;
            });
            
            t1.start();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t2.start();
        }
    }
    

    a未加volatile修饰,该程序在线程t2中修改了a的值,无法同步到线程t1的工作内存中,导致t1无法终止,程序无法结束。如果在a的前面加上volatile修饰,则可以将t2线程修改的值刷新到主存,同时t1能读取到值的修改,程序能正常结束。

    相关文章

      网友评论

        本文标题:Java的volatile与synchronized关键字使用对

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