volatile关键字
可见性
所谓可见性,就是指一个线程改变了共享变量之后,其他线程能够立即知道这个变量被修改。我们知道在Java内存模型中,不同线程拥有自己的本地内存,而本地内存是主内存的副本。如果线程修改了本地内存而没有去更新主内存,那么就无法保证可见性。
退不出的循环
public class ThreadTest2 {
private static boolean run= true;
private static int i = 0;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
System.out.println("t1开始执行");
while(run){
i++;
}
System.out.println("t1结束执行" + i);
},"t1").start();
Thread.sleep(1000);
state = false;
}
}
main线程对run变量进行了修改,结果t1线程却依然无法停止
为什么?
- 初始状态,t线程刚开始从主内存读取了run的值到工作内存

- 因为t线程要频繁从主内存中读取run的值,JIT编译器会将state的值缓存至自己工作内存中的高速缓存中,减少对主存中run的访问,提高效率

- 1s之后,main线程修改了run的值,并同步至主存,而t是从自己工作内存中的高速缓存中读取这个变量的值,结果永远是久值

解决方法
volatile(易变)关键字
private static volatile boolean state = true;
- 它可以用来修饰成员变量和静态成员变量,它可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作volatile变量都是直接操作主存
或者加synchronized关键字
注意:
前面例子体现的实际是可见性,他保证的是在多个线程之间,一个线程对volatile变量的修改对另一个线程可见,不能保证原子性,仅用在一个写线程,多个读线程的情况
synchronized语句块既可以保证代码块的原子性,也可以保证代码块内变量的可见性,但缺点是synchronized是属于重量级操作,性能相对更低
为什么synchronized关键字可以保证可见性呢?
synchronized在修改了本地内存中的变量后,解锁前会将本地内存修改的内容刷新到主内存中,确保了共享变量的值是最新的,也就保证了可见性
如果说在前面实例的死循环中加入System.out.println();会发现即使不加volatile修饰符,线程t也能正确看到对run变量的修改,为什么?
- 通过看
System.out.println();的源码不难发现
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
println方法内部实际上也是加了synchronized关键字的,因此可以保证可见性和原子性
volatile原理
volatile的底层实现原理是内存屏障,Memory Barrier
- 对volatile变量的写指令后会加入写屏障
- 对volatile变量的读指令前会加入读屏障
如何保证可见性
- 写屏障保证在该屏障之前的,对共享变量的改动,都同步到主存当中
public void actor1(I_Result r) {
num = 2;
ready = true; //ready是 volatile 赋值 带写屏障
// 写屏障
}
- 而读屏障保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据
public void actor1(I_Result r) {
//读屏障
// ready是 volatile 读取值带读屏障
if(ready) {
r.r1 = num + num;
} else {
r.r1 = 1;
}
}
如何保证有序性
- 写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
public void actor1(I_Result r) {
num = 2;
ready = true; //ready是 volatile 赋值 带写屏障
// 写屏障
}
- 而读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前
public void actor1(I_Result r) {
//读屏障
// ready是 volatile 读取值带读屏障
if(ready) {
r.r1 = num + num;
} else {
r.r1 = 1;
}
}
注意:
- 写屏障仅仅是保证之后的读能够读到最新的结果,但不能保证读跑到他前面
- 而有序性的保证也只是保证了本线程内相关代码不被重排序
即只能保证有序性,可见性,不保证原子性
double-checked lock问题
以著名的double-checked locking单例模式为例
final class Singleton {
private Singleton() {
}
private static Singleton singleton = null;
public static Singleton getInstance() {
//再加一层check,解决synchronized效率低的问题
if (singleton == null) {
//首次访问会同步,而之后的使用没有synchronized
synchronized (Singleton.class) {
//加锁解决线程安全问题,
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
以上实现的特点是:
- 懒惰实例化
- 首次使用getInstance()才使用synchronized加锁,后序使用时无需加锁
- 但很关键一点:第一个if使用了singleton变量,是在同步代码块之外的
解决方法:
在singleton变量上加上 volatile 关键字
final class Singleton {
private Singleton() {
}
private static volatile Singleton singleton = null;
public static Singleton getInstance() {
//再加一层check,解决synchronized效率低的问题
if (singleton == null) {
//首次访问会同步,而州的使用没有synchronized
synchronized (Singleton.class) {
//加锁解决线程安全问题,
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
happens-before
happens-before规定了对共享变量的写操作对其他线程的读操作可见,它是可见性与有序性的一套规则总结,抛开以下happens-before规则,JVM并不能保证一个线程对共享变量的写,对于其他线程对该共享变量的读可见
- 线程解锁A之前对变量的写,对于接下来对A加锁的其他线程对该变量的读可见
public class Test {
private static final Object A = new Object();
private static int x;
public static void main(String[] args) {
new Thread(() -> {
synchronized (A){
x=10;
}
},"t1").start();
new Thread(() -> {
synchronized (A){
System.out.println(x);
}
},"t2").start();
}
}
- 线程对volatile变量的写,对接下来其他线程对该变量的读可见
public class Test {
private static volatile int x;
public static void main(String[] args) {
new Thread(() -> {
x=10;
},"t1").start();
new Thread(() -> {
System.out.println(x);
},"t2").start();
}
}
- 线程 start前对变量的写,对该线程开始后对该变量的读可见
public class getClassTest {
private static volatile int x;
public static void main(String[] args) {
x = 10;
new Thread(() -> {
System.out.println(x);
},"t2").start();
}
}
- 线程结束前对变量的写,对其他线程得知他结束的读可见(比如其他线程调用t1.isAlive()或t1.join()等待他结束)
public class getClassTest {
private static int x;
public static void main(String[] args) throws InterruptedException {
Thread t2 = new Thread(() -> {
x = 10;
},"t2");
t2.start();
t2.join();
System.out.println(x);
}
}
- 线程t1打断t2(interrupt)前对变量的写,对于其他线程得知t2被打断后对变量的读可见(通过t2.interrupted或t2.isInterrupted)
public class getClassTest {
private static int x;
public static void main(String[] args) throws InterruptedException {
Thread t2 = new Thread(() -> {
while(true){
if (Thread.currentThread().isInterrupted()){
System.out.println(x);
break;
}
}
},"t2");
t2.start();
new Thread(() ->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
x = 10;
t2.interrupt();
},"t1").start();
while (!t2.isInterrupted()) {
Thread.yield();
}
System.out.println(x);
}
}
- 对变量默认值(0,false,null)的写,对其他线程对该变量的读可见
- 具有传递性,如果x hb-> y 并且 y hb -> z那么有x hb -> z,配合volatile的防指令重排,有下面的例子
public class getClassTest {
private static int x;
static int y ;
public static void main(String[] args) throws InterruptedException {
Thread t2 = new Thread(() -> {
y = 10;
x = 20;
//------写屏障------
},"t2");
t2.start();
Thread t1 = new Thread(() -> {
//------读屏障------
System.out.println(x);
System.out.println(y);
},"t2");
t1.start();
}
}
线程安全单例
单例模式有很多种实现方式,饿汉,懒汉,静态内部类,枚举类,那么哪些是线程安全的呢
饿汉式: 类加载就会导致该单例对象被创建
懒汉式:类加载不会导致该单例对象被创建,而是首次使用该对象时才会创建
实现1:
//类加final的原因:防止创建子类使得方法被子类重写
//如果实现了序列化接口,则需要再创建一个public Object readResolve()方法来防止通过反序列化来创建对象
final class Singleton1 implements Serializable {
//防止通过构造方法来创建对象,但是不能防止反射创建新的实例
private Singleton1() {
}
//静态成员变量在类加载阶段进行赋值,类加载阶段是有锁的,可以保证线程安全
private static final Singleton1 singleton1 = new Singleton1();
//为什么提供静态方法而不是直接将 单例对象设置为public的?
//1. 提供更好的封装性
//2. 能够提供泛型的支持
public static Singleton1 getInstance() {
return singleton1;
}
public Object readResolve(){
return singleton1;
}
}
实现2:
enum Singleton {
singleton2;
public void whateverMethod(){
}
}
实现3:
public final class Singleton {
private Singleton() {}
private static Singleton singleton3 = null;
//这里线程安全,但是只有第一次创建线程会出现线程安全问题,之后的每次创建都会被synchronized锁住,效率低
public static synchronized Singleton getInstance(){
if(singleton != null){
return singleton3;
}
singleton3 = new Singleton();
return singleton3;
}
}
实现4:
public final class Singleton{
private Singleton() {}
//构造方法的指令和赋值指令可能发生重排序,加入volatile关键字阻止synchronized内重排序
private static volatile Singleton singleton4 = null;
public static Singleton getInstance(){
//解决频繁synchronized效率低的问题
if(singleton4 ==null){
synchronized(Singleton.class){
//解决首次创建单例的并发问题
if(singleton4 == null){
singleton4 = new Singleton();
}
}
}
return singleton4;
}
}
实现5:
public final class Singleton {
private Singleton() {}
//静态内部类只有在调用的时候才会被加载
private static class lazyHolder{
static final Singleton singleton5 = new Singleton();
}
//线程安全
public static synchronized Singleton getInstance(){
return lazyHolder.singleton5;
}
}

浙公网安备 33010602011771号