作者简介:悟空,8 年一线互联网开发和架构经验,用故事讲解分布式、架构设计、Java 核心技术。《JVM 性能优化实战》专栏作者,开源了《Spring Cloud 实战 PassJava》项目,公众号:悟空聊架构。本文已收录至 www.passjava.cn

volatile和synchronzied的区别

  • volatile只能修饰实例变量和类变量,synchronized可以修饰方法和代码块。
  • volatile不保证原子性,而synchronized保证原子性
  • volatile 不会造成阻塞,而synchronized可能会造成阻塞
  • volatile 轻量级锁,synchronized重量级锁
  • volatile 和synchronized都保证了可见性和有序性

volatile 小结

  • volatile 保证了可见性:当一个线程修改了共享变量的值时,其他线程能够立即得知这个修改。
  • volatile 保证了单线程下指令不重排:通过插入内存屏障保证指令执行顺序。
  • volatitle不保证原子性,如a++这种自增操作是有并发风险的,比如扣减库存、发放优惠券的场景。
  • volatile 类型的64位的long型和double型变量,对该变量的读/写具有原子性。
  • volatile 可以用在双重检锁的单例模式种,比synchronized性能更好。
  • volatile 可以用在检查某个状态标记以判断是否退出循环。

补充案例

volatile为什么不保证原子性,在单线程的场景,答案是20000,如果是多线程的场景下呢?答案是可能是20000,但很多情况下都是小于20000。

怎么保证输出结果是20000呢?

synchronized同步代码块

我们可以通过使用synchronized同步代码块来保证原子性。从而使结果等于20000

public synchronized static void increase() {
   number++;
}

synchronized同步代码块执行结果

但是使用synchronized太重了,会造成阻塞,只有一个线程能进入到这个方法。我们可以使用Java并发包(JUC)中的AtomicInterger工具包。

AtomicInterger原子性操作

我们来看看AtomicInterger原子自增的方法getAndIncrement()

AtomicInterger

public static AtomicInteger atomicInteger = new AtomicInteger();

public static void main(String[] args) {

    for (int i = 0; i < 20; i++) {
        new Thread(() -> {
            for (int j = 0; j < 1000; j++) {
                atomicInteger.getAndIncrement();
            }
        }, String.valueOf(i)).start();
    }

    // 当所有累加线程都结束
    while(Thread.activeCount() > 2) {
        Thread.yield();
    }

    System.out.println(atomicInteger);
}

多次运行的结果都是20000。

getAndIncrement的执行结果