Java 可见性
内存模型
主存
所有线程都可以访问
本地内存
每个线程私有的内存
- java 的所有变量都存储在主内存中
- 每个线程有自己独的工作内存,保存了该线程使用到的变量副本,是对主内存中变量的一份拷贝
- 每个线程不能访问其他线程的工作内存,线程间变量传递需要通过主内存来完成
- 每个线程不能直接操作主存,只能把主存的内容拷贝到本地内存后再做操作(这是线程不安全的本质),然后写回主存
可见性的方法
volatile
这种方式可以保证每次取数直接从主存取
它只能保证内存的可见性,无法保证原子性
它不需要加锁,比 synchronized 更轻量级,不会阻塞线程
验证测试
public class testRun implements Runnable{ public volatile Boolean flag=false; public ArrayList<String> arr =new ArrayList<>(); @Override public void run() { while (true){ if(arr.size()>0){ break; } } } }
public static void main(String[] args) throws InterruptedException {
testRun testRun = new testRun();
new Thread(testRun).start();
Thread.sleep(1000);
testRun.flag=true;
testRun.arr.add("123");
}
测试 发现arr 不加volatile 主线程的修改不能使 测试线程正确停止
ArrayList 是线程不安全的 如果我们使用线程安全类呢
public class testRun implements Runnable{
public volatile Boolean flag=false;
public ArrayList<String> arr =new ArrayList<>();
public Vector<String> vec =new Vector<>();
@Override
public void run() {
while (true){
if(vec.size()>0){
break;
}
}
}
}
public static void main(String[] args) throws InterruptedException {
testRun testRun = new testRun();
new Thread(testRun).start();
Thread.sleep(1000);
testRun.flag=true;
testRun.vec.add("123");
}
测试发现是可以正确停止
那
public HashMap<String,Object> has =new HashMap<>();
public Hashtable<String,Object> hashtable =new Hashtable<String,Object>();
public ConcurrentHashMap<String,Object> concurrentHashMap =new ConcurrentHashMap<String,Object>();
测试发现 HashMap 需要加volatile 关键字才可以正确停止 线程安全的hashtable与concurrentHashMap则不需要 具体为啥 不清楚
关于 volatile 的影响范围
不管事基本数据类型像 int 还是引用数据类型 像 String 或者自己创建的 实体类Objext 在被共享访问时都存在可见性问题
两个有趣的demo
public class TestDemo3 {
public int i=1;
}
public class Test1 {
static volatile TestDemo3 t3=new TestDemo3();
static TestDemo3 t4=t3;
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
while (true){
if(t4.i!=1){
break;
}
}
}
},"main1").start();
Thread.sleep(500);
new Thread(new Runnable() {
@Override
public void run() {
t3.i=2;
}
},"main2").start();
}
}
这种情况为什么不能解决可见性问题
原因 volatile 的可见性是通过 读屏障来实现的 获取 t4.i 的值 因为 t4 变量没有加volatile 导致 没有 加读屏障所以获取的值 可见性
这种情况为什么又能保证可见性
public class TestDemo3 {
public int i=1;
}
public class Test1 {
static volatile TestDemo3 t3=new TestDemo3();
static TestDemo3 t4=t3;
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
while (true){
if(t3.i!=1){
break;
}
}
}
},"main1").start();
Thread.sleep(500);
new Thread(new Runnable() {
@Override
public void run() {
t4.i=2;
}
},"main2").start();
}
}
分析
主存的数据同步是根据写屏障 实现的 理论上 t4.i=2;赋值因为 t4没有加 volatile关键字 那么
不会同步到主存中 但实际 这个demo仍然能保证可见性不知道为什么