JAVA多线程中的volatile和synchronized介绍

164 阅读3分钟

这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战

1. 前言

我们在开发多线程程序的过程中,少不了与volatilesynchronized这两个关键字打交道。下面我们就来介绍下关于这两个关键字的一些知识点。

2. volatile

在了解volatile之前,我们先了解一下java的内存模型的运行模式。

(1)每个线程都有自己的本地内存空间(java栈中的帧)。线程执行时,先把变量从内存读到线程自己的本地内存空间,然后对变量进行操作。 
(2)对该变量操作完成后,在某个时间再把变量刷新回主内存。

因此,如果有变量是对所有线程共享的,当我们修改了它的值时,其他线程也对这个值进行操作,就很容易产生多线程的问题。

volatile是一个类型修饰符(type specifier)。它是被设计用来修饰被不同线程访问和修改的变量。确保本条指令不会因编译器的优化而省略,且要求每次直接读值。

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

  1. 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

  2. volatile禁止进行指令重排序,所以能在一定程度上保证有序性。( JVM 具有指令重排的特性,可能会将原本执行顺序为1->2->3的指令变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致⼀个线程获得还没有初始化的实例)。

当对volatile标记的变量进行修改时,会将其他缓存中存储的修改前的变量清除,然后重新读取。volatile一种弱的同步机制,并不能保证线程安全。

3. synchronized

synchronized 关键字解决的是多个线程之间访问资源的同步性, synchronized 关键字可以保证被它修饰的⽅法或者代码块在任意时刻只能有⼀个线程执⾏。被synchronize关键字修饰的代码或者方法,会被有一把“同步锁”,只有获得同步锁的线程才能够执行同步代码,而未获得同步锁的线程会不停地尝试去获取同步锁,直到获得为止,当多个线程都在等待获取同一把同步锁时,就会出现锁竞争的现象,由CPU分配下一次获得锁的线程。

由于同步锁的存在,因此synchronized可保证一个线程的变化(主要是共享数据的变化)被其他线程所看到(保证可见性,完全可以替代volatile功能)。而且synchronized限制每次只有一个线程可以访问同步块,因此,无论同步块内的代码如何被乱序执行,只要保证串行语义一致,那么执行结果总是一样的,因此synchronized时线程安全的。

4. 总结

  1. synchronize可以保证操作原子性,且保证内存可见性;volatile仅能保证内存可见性。

  2. volatile不需要加锁,比synchronized更轻量级,不会阻塞线程。

  3. volatile适用场景为对变量的写入操作不依赖其当前值、或者该变量没有包含在具有其他变量的不变式中。