Java的volatile与synchronized关键字使用对比

异同

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关键字使用对比)