本章主要讲解共享对象
1、对于可见性的认识:
.复杂
.这里是指在多线程访问共享变量条件下,当一个线程对于共享变量修改后,其他线程也能适时的看到。这一可见性针对的是共享变量(内存)、多线程。
2、示例代码:
public class NoVisibility {
private static boolean ready;
private static int number;
private static class ReaderThread extends Thread {
private int i;
ReaderThread(int i) {
this.i = i;
}
@Override
public void run() {
while (!ready) {
// 将当前线程的由运行状态变为准备状态
Thread.yield();
System.out.println("线程名:" + Thread.currentThread().getName() + "| i : " + i + "|"
+ number);
}
}
}
public static void main(String[] args)
throws InterruptedException {
// 将for循环和 System.out.println("i - " + i + " - " + number);注释掉为原文给的示例代码
for (int i = 0; i < 10000; i++ ) {
new ReaderThread(i).start();
number = 42;
System.out.println("i - " + i + " - " + number);
ready = true;
}
}
}
这段代码是我在原文的基础上修改的,实验的结果如下(展示的结果是在随机的,并不是一次就能出来一下结果):
i - 0 - 42
线程名:Thread-0| i : 0|42
i - 1 - 42
i - 2 - 42
i - 3 - 42
i - 4 - 42
i - 5 - 42
i - 6 - 42
i - 7 - 42
i - 8 - 42
i - 9 - 42
i - 10 - 42
修改原文中程序的原因是,原文的程序一直没有等到结果,但是在此程序的基础上可以分析了,在main方法中是对堆区共享数据number,ready的修改,展示的结果就是当main方法中还没来得及对ready值进行修改,线程就开始执行了,所以会出现打印“线程名:Thread-0| i : 0|42”的语句。此程序是一个线程修改,一个线程读取。
书中甚至给出可能情况是:number的值可能为零,这也是不难理解了。从而引入“重排序”概念,并做了特别的注释:
在没有同步情况下,编译器、处理器及运行时等都可能对操作的执行顺序进行一些意想不到的调整。
在缺乏足够同步的多线程程序中,要想对内存操作的执行顺序进行判断,几乎无法得到正确的结论。
个人对此概念的理解是:在对多线程程序中,要想得到预想的结果,必须添加正确的同步。
3.1.1 失效数据
该类数据产生的原因是:在并发程序中未能使用正确的同步机制,导致共享数据状态未能及时更新,而被使用。比如上述实验中的ready就是失效数据导致。书中同时列举另一个重要的例子,在非线程安全类中的get/set方法都是用sychronized来避免出现失效数据。这里补充一点就是失效数据依然是某个线程产生的数据,还是具有一定的意义。(为下一小节做铺垫)
3.1.2 非原子的64位操作
书中引出一个概念:最低安全性,是指当线程在没有同步的情况下读取变量,可能会得到失效数据,但依然是由之前的某个线程设置的值,并不是随机值(这里是重点),称为最低安全性。
但是有例外,就是非volatile类型的long、double数据,JVM允许将64位的读或写操作分解为两个32位操作(本质原因)。当对非volatile的long/double数据的读写操作在不同线程中,那么很可能读取到某个值的高32位和另外一个值的低32位。故而得出在不考虑失效数据问题,在多线程中使用非volatile的long、double数据也是不安全的,除非使用volatile声明,或加锁。
3.1.3 加锁与可见性
文中给了以下注释:
加锁的含义不仅仅局限于互斥行为,还包括可见性。
为了确保所有线程都能看到共享变量的最新值,所有读写操作
的线程,都必须在同一个锁上同步
3.1.4 Volatile变量
1、Volatile变量是相对sychronized轻量级的同步机制,原因有二:不会加锁;不会阻塞。
2、确保变量更新操作会通知到其他线程,对其读取总是会返回最新写入的值。(不会被寄存器缓存或处理器看不到的地方)
3、不会将对改变量的操作与其他内存操作一起重排序
4、与加锁机制的区别:加锁机制既可以确保原子性又可以确保可见性,但是Volatile只能确保可见性。
5、不建议滥用。经典用法:用来描述状态的变量。
6、使用条件(书中给出严格的声明)——当前仅当——满足一下——所有——条件:
1、当变量的写入操作不依赖变量当前的值,或者确保只有单线程更新变量的值
2、该变量不会与其他状态变量一起纳入不变性条件中
3、访问变量时不需要加锁