上一篇文章,编哥说了一些避免线程不安全的手段,关注点是对状态访问的限制,比如通过 synchronized 关键字
本章节,更关注从根上保证安全,也就是从对象本身出发,我们要设计一个安全地共享出去的对象
需要有什么原则呢?如果你要安全地共享你的对象给其他人用,可以关注下面4点
- 可见性
- 发布和逸出的风险
- 线程封闭
- 不可变性 和 安全发布
我们先看看第一个
可见性
我们除了可以使用synchronized 来划定 临界区,从而保证 阻塞 执行以及原子性,但是我们还要理解它背后还包含的:可见性
一个 可见性 的例子
class NoVisibility {
static boolean ready;
static int number;
static class ReaderThread extends Thread {
public void run() {
while (!ready) Thread.yield(); // tt1 有可能 读取的 ready 一直是 false
System.out.println(number);
}
}
public static void main(String[] args) {
new ReaderThread().start();
number = 42;
ready = true; // tt1 那里或许看不到 这里 变为 true 了
}
}
// 结果是 tt1 那里会一直循环
// 或者 令人惊奇 地打印0,而非42(这就是 重排序 的现象; reordering)
synchronized 除了之前我们说的可以保证原子性,其实现在我们知道它也可以保证可见性,只要你把要读写的数据放入 synchronized 块(临界区)即可
这意味着,我们对 number 和 ready 变量的读写,应该用 synchronized 块包裹,如下意思:
class SynchronizedVisibility {
static Object lock;
static boolean ready;
static int number;
static class ReaderThread extends Thread {
public void run() {
synchronized (lock) {
while (!ready) Thread.yield(); // tt1 有可能 读取的 ready 一直是 false
System.out.println(number);
}
}
}
public static void main(String[] args) {
new ReaderThread().start();
synchronized (lock) {
number = 42;
ready = true; // tt1 那里或许看不到 这里 变为 true 了
}
}
}
volatile 变量是另外一个有名的选择,它也可以保证可见性,也是被称作较弱的同步形式;
代码就是如下样子:
class VolatileVisibility {
volatile static boolean ready;
volatile static int number;
//... 其他的和第一版NoVisibility一样
}
需要注意的是:volatile 不能保证原子性,只能保证可见性,比如 volatile 变量 count++ ,这个复合操作还是可能被拆成几步,来进行执行,后加动作或许因此变得不那么立即可见
总结
如果我们对对象的各种属性的可见性进行了合适的设定,那么我们的这个对象,就是可以安全的发布的一个必要的步骤
下一篇,编哥将会说一说:发布对象时它逸出的风险