JAVA并发编程补充-线程可见性

Posted Gggoblin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA并发编程补充-线程可见性相关的知识,希望对你有一定的参考价值。

定义一个类Visibility1包含两个变量

public class Visibility1 {
public static boolean ready;
public static int number;
}
自定义一个线程类,当Visibility1中的ready属性为false时进入循环打印number
public class ReaderThread extends Thread{

@Override
public void run() {
while (!Visibility1.ready){
Thread.yield();
System.out.println(Visibility1.number);
}
}
}
测试线程
public class TestThread1 {
public static void main(String[] args) {
new ReaderThread().start();
Visibility1.number = 42;
Visibility1.ready = true;
}
}
输出结果有三种:
0:这种情况是先进入了循环执行了一次输出;
42:这种情况是number被赋值42之后进入的循环;
空:这种情况是ready直接被赋值为true,没有进入循环;
这里涉及到一个指令排序的问题,例如:int a = 1; int b = 2; int c = a+b;这三条语句cpu有可能先执行第二条,但不会先执行第三条;(cpu的优化手段,有可能的第二条执行的时间比第一条短)。

这里写了一个测试类Visibility其中有一个bChanged变量,两个无限循环的线程,具体看注解,理论上说这个程序是会停止的,但是执行结果确实没有停止
public class Visibility {

private static boolean bChanged;

public static void main(String[] args) throws InterruptedException {

/**
* 第一个线程,当bChanged为true的时候输出 "!=" 然后退出java虚拟机
*/
new Thread(()-> {
for ( ; ; ) {
if (bChanged == true){
System.out.println("!=");
System.exit(0);
}
}
}).start();

/**
* 第二个线程,将本次、bChanged赋值为true
*/
Thread.sleep(10);
new Thread(()-> {
for (;;){
bChanged = true;
}
}).start();
}
}
原因如下图,由于cpu运行太快在cpu内存之间还有一层缓存,进入死循环没有退出是因为第一个线程的变量是读取的缓存。

解决方法:使用 volatile关键字:1保证变量的修改让所有线程可见;2阻止指令排序;

     volatile是比较古老的关键字   synchroized已经优化的很好了,不要去刻意的使用volatile

private static volatile boolean bChanged;

sync能够解决可见性、原子性 volatile只能解决               如果CAS操作没有原子化多线程操作一定会有问题  先比较在修改CAS操作    compare  and  set

线程并发问题:写或者读到了一个过期的数据,解决方式

1.线程封闭:final   不要线程之间共享变量      (约等于没用过)

2.栈封闭:比如说方法内部声明,修改  不会溢出,方法执行完就释放,不存在于内存

3.ThreadLocal线程绑定。

 

以上是关于JAVA并发编程补充-线程可见性的主要内容,如果未能解决你的问题,请参考以下文章

[Java并发编程之美]第2章 并发编程的其他基础知识 补充知识

[Java并发编程实战] 共享对象之可见性

[Java并发编程实战] 共享对象之可见性

JAVA并发编程递进篇,探索线程安全性volatile关键字如何保证可见性

JAVA并发编程递进篇,探索线程安全性volatile关键字如何保证可见性

Java 并发编程 -- 并发编程线程基础(线程安全问题可见性问题synchronized / volatile 关键字CASUnsafe指令重排序伪共享Java锁的概述)