volatile 与内存可见性
volatile
是 Java 虚拟机提供的轻量级的同步机制,它有三大特性:
什么是内存可见性问题?
内存可见性问题是,当多个线程操作共享数据时,彼此不可见。
内存可见性问题的产生
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| class ThreadDemo implements Runnable {
private boolean flag = false;
public boolean isFlag() { return flag; }
@Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } flag = true; System.out.println(Thread.currentThread().getName() + " flag = " + flag); }
}
public class VolatileTest {
public static void main(String[] args) { ThreadDemo threadDemo = new ThreadDemo();
new Thread(threadDemo, "T1").start();
while (true) { if (threadDemo.isFlag()) { System.out.println(Thread.currentThread().getName() + " flag = " + threadDemo.isFlag()); break; } } }
}
|
上述代码输出 T1 flag = true
后,主线程会一直卡死在 while
循环的执行中,因为主线程感知不到 T1 线程对共享变量 flag
的更改。
内存可见性问题的解决
volatile 解决内存可见性问题
在多线程环境下,使用 volatile
关键字,可以保证内存可见性和禁止指令重排。特别注意,volatile 不能保证原子性,即多个线程同时操作共享数据时,存在线程安全问题,最终导致数据不一致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| class ThreadDemo implements Runnable {
private volatile boolean flag = false;
public boolean isFlag() { return flag; }
@Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } flag = true; System.out.println(Thread.currentThread().getName() + " flag = " + flag); }
}
public class VolatileTest {
public static void main(String[] args) { ThreadDemo threadDemo = new ThreadDemo();
new Thread(threadDemo, "T1").start();
while (true) { if (threadDemo.isFlag()) { System.out.println(Thread.currentThread().getName() + " flag = " + threadDemo.isFlag()); break; } } }
}
|
程序执行输出的结果:
1 2
| T1 flag = true main flag = true
|
synchonrized 解决内存可见性问题
synchronized
关键字的作用是让线程在进入方法或者同步代码块时自动获取锁,在退出时自动释放锁。当一个线程获取了锁之后,它会清空工作内存并从主内存中重新读取共享变量的值,这确保了线程读取到的变量值是最新的。当线程释放锁时,会将其在工作内存中对共享变量的修改刷写回主内存,以使其他线程可见。所以,使用 synchronized
也可以解决内存可见性问题,其底层是通过锁机制确保线程之间的同步和内存可见性。特别注意,由于 synchronized
是独占锁,在高并发场景下的性能较低,因此建议使用 volatile
来解决内存可见性问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| class ThreadDemo implements Runnable {
private boolean flag = false;
public boolean isFlag() { return flag; }
@Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } flag = true; System.out.println(Thread.currentThread().getName() + " flag = " + flag); }
}
public class VolatileTest {
public static void main(String[] args) { ThreadDemo threadDemo = new ThreadDemo();
new Thread(threadDemo, "T1").start();
while (true) { synchronized (threadDemo) { if (threadDemo.isFlag()) { System.out.println(Thread.currentThread().getName() + " flag = " + threadDemo.isFlag()); break; } } } }
}
|
程序执行输出的结果:
1 2
| T1 flag = true main flag = true
|