1 问题的引出
局部变量跨线程,又不能用volatile,怎么保证其可见性
2 是否真的能有局部变量跨线程写入?
看一下这段代码:
public static void main(String []f) { Integer integer = 1; new Thread(new InnerThread(integer)).start(); while(true) { if(integer == 2) break; } } private static class InnerThread implements Runnable { private Integer innerInteger; public InnerThread(Integer integer) { this.innerInteger = integer; } @Override public void run() { innerInteger = 2; } }
这个代码有没有可见性问题?答案是没有的
两条线程访问的其实是2个变量,是没有线程并发可见性问题的,因为java不像c++那样有变量的引用语法,&integer,子线程的构造其实是c++里面的值传递而不是引用传递,什么叫值传递?引用传递?
public static void main(String []f) { Integer x = 1; setInt(x); System.out.println(x); } private static void setInt(Integer x) { x = 2; } 打印1,值传递,打印2,引用传递
子线程写入的是线程内部的局部对象,主线程读取的是main方法的局部对象,根本是2个变量,自然无可见性问题
3 那么有没有使用一个局部对象的多线程写入?答案是有的
public static void main(String []f) { Integer integer = 1; new Thread(new Runnable() { @Override public void run() { integer = 2; } }).start(); while(true) { if(integer == 2) break; } }
然而,这个代码是过不了编译的,必须在integer上加上final
public static void main(String []f) { final Integer integer = 1; new Thread(new Runnable() { @Override public void run() { integer = 2; } }).start(); while(true) { if(integer == 2) break; } }
然而加上final后,integer=2自然非法了
所以归根结底jdk从语法上避免了这个问题的发生
5 扩展思考
public class TestMain { private volatile TestC testC; private static class TestC { private Integer integer; public Integer getInteger() { return integer; } public void setInteger(Integer integer) { this.integer = integer; } } }
这个代码能保证integer的可见性吗,答案是不能,volatile修饰的是testC,而不是integer,这个性质有点像Unsafe控制ConcurrentHashMap内并发数组元素的可见性 ,数组对象volatile,数组元素没有volatile,数组元素一样会有可见性问题
6
public static void main(String []f) { TestC testC = new TestC(); testC.setInteger(1); new Thread(new Runnable() { @Override public void run() { testC.setInteger(2); } }).start(); while(true) { if(testC.getInteger() == 2) break; } } private static class TestC { private Integer integer; public Integer getInteger() { return integer; } public void setInteger(Integer integer) { this.integer = integer; } }
这个代码是否实现了第2点所说的“局部变量跨线程写入”?
对不起,这个不属于写入,这里的“写入”指的是,让共享变量作为=的左值,此之谓“写入”,比如:
public static void main(String []f) { TestC testC = new TestC(); testC.setInteger(1); new Thread(new Runnable() { @Override public void run() { TestC tmp = new TestC(); tmp.setInteger(2); testC = tmp; } }).start(); while(true) { if(testC.getInteger() == 2) break; } } private static class TestC { private Integer integer; public Integer getInteger() { return integer; } public void setInteger(Integer integer) { this.integer = integer; } }
此时又会报,要加final的提示
我们得到重要结论:局部变量永远不能被多线程写入,既然无法多线程写入,自然也没有可见性问题