作者简介:悟空,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太重了,会造成阻塞,只有一个线程能进入到这个方法。我们可以使用Java并发包(JUC)中的AtomicInterger工具包。
AtomicInterger原子性操作
我们来看看AtomicInterger原子自增的方法getAndIncrement()
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。