定义:如果线程A中的一个操作对线程B是可见的,那么这个操作的执行成功后结果将立刻被线程B发现。
image.png
多核时代,每颗 CPU 都有自己的缓存,当多个线程在不同的 CPU 上执行时,这些线程操作的是不同的 CPU 缓存。线程 A 操作的是 CPU-1 上的缓存,而线程 B 操作的是 CPU-2 上的缓存,很明显,这个时候线程 A 对变量 V 的操作对于线程 B 而言就不具备可见性了。
在多线程的环境下,如果某个线程首次读取共享变量,则首先到主内存中获取该变量,然后存入工作内存中,以后只需要在工作内存中读取该变量即可。同样如果对该变量执行了修改的操作,则先将新值写入工作内存中,然后再刷新至主内存中。但是什么时候最新的值会被刷新至主内存中是不太确定,一般来说会很快,但具体时间不知。
要解决共享对象可见性这个问题,我们可以使用volatile关键字或者是加锁


可见性可以解决,但是很难被证明出来!网上的例子都是错的。

一个常见的可见性的例子

实际上这个例子不是可见性的问题,因为在while里面加入一句输出hello,(System.out.println()里面有synchornized),线程会停止运行。
参见 hllvm-group.iteye.com/group/topic/34932

  1. public class Test {
  2. // volatile
  3. static boolean flag = true;
  4. public static void main(String[] args) throws InterruptedException {
  5. new Thread(()->{
  6. while(flag){
  7. }
  8. }).start();
  9. TimeUnit.SECONDS.sleep(1);
  10. new Thread(()->{
  11. flag=false;
  12. }).start();
  13. }
  14. }

而是编译优化指令重排引起的问题,
image.png

一个关于可见性的问题

直接运行,进不去if。
放开这段代码中的注释,就能进入到if中。
不使用for循环直接new Object(); 也进不去。
原因未知。

  1. public class Test2 {
  2. public static void main(String[] args) throws InterruptedException {
  3. MyThread t = new MyThread();
  4. t.start();
  5. while(true){
  6. // for (int i = 0; i < 2; i++) {
  7. // new Object();
  8. // }
  9. if(t.getFlag()){
  10. System.out.println("主线程进入循环执行~~~~~");
  11. }
  12. }
  13. }
  14. }
  15. class MyThread extends Thread{
  16. private boolean flag = false;
  17. @Override
  18. public void run() {
  19. try {
  20. TimeUnit.SECONDS.sleep(1);
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. flag = true;
  25. System.out.println("flag="+flag);
  26. }
  27. public boolean getFlag() throws InterruptedException {
  28. return flag;
  29. }
  30. public void setFlag(boolean flag) {
  31. this.flag = flag;
  32. }
  33. }

将new Object(); 换成 int ,进不去

  1. public static void main(String[] args) throws InterruptedException {
  2. MyThread t = new MyThread();
  3. t.start();
  4. while(true){
  5. for (int i = 0; i < 2; i++) {
  6. int ss = 1;
  7. }
  8. if(t.getFlag()){
  9. System.out.println("主线程进入循环执行~~~~~");
  10. }
  11. }
  12. }

再将for循环的次数改成1 ,能进去

  1. public static void main(String[] args) throws InterruptedException {
  2. MyThread t = new MyThread();
  3. t.start();
  4. while(true){
  5. for (int i = 0; i < 1; i++) {
  6. int ss = 1;
  7. }
  8. if(t.getFlag()){
  9. System.out.println("主线程进入循环执行~~~~~");
  10. }
  11. }
  12. }