Effective Java读书笔记二二(Java Tips.Day.22)
来源:互联网 发布:淘宝代销管理软件 编辑:程序博客网 时间:2024/05/23 01:57
TIP 66 同步访问共享的可变数据
关键字synchronized可以保证在同一时刻,只有一个线程可以执行某一个方法,或者某一个代码块
读或写一个变量是原子的,除非这个变量的类型是long或者double。
然而,即使原子操作,如果没有使用同步,仍然是错误的、非常危险的。
下面来看示例代码:
public class Main { private static boolean stopRequested = false; public static void main(String[] args) throws InterruptedException { Thread backgroundThread = new Thread(() -> { int i = 0; while (!stopRequested){ i++; } }); backgroundThread.start(); TimeUnit.SECONDS.sleep(1); stopRequested = true; }}
你可能期待程序运行大约1秒之后,线程内的循环终止。
然而事实上,线程会一直运行,永不停止。
问题就在于,由于没有同步,就不能保证后台线程何时“看到”主线程对stopRequested 的值所做的改变。
没有同步,虚拟机会将代码优化,从
while (!stopRequested){ i++;}
优化成这样:
if (!stopRequested){ while (true){ i++; }}
于是子线程只会判断一次stopRequested的值,然后就会进入死循环。
解决方法是同步访问stopRequested,这个程序会在大约一秒钟之后如期停止:
public class Main2 { private static boolean stopRequested = false; public static synchronized boolean isStopRequested() { return stopRequested; } public static synchronized void setStopRequested(boolean stopRequested) { Main2.stopRequested = stopRequested; } public static void main(String[] args) throws InterruptedException { Thread backgroundThread = new Thread(() -> { int i = 0; while (!isStopRequested()){ i++; } if (!stopRequested){ while (true){ i++; } } }); backgroundThread.start(); TimeUnit.SECONDS.sleep(1); setStopRequested(true); }}
读和写方法都被同步了,只同步写方法是不够的! 读/写方法必须都同步,否则,子线程循环依然停不下来!
读写方法内的动作都是原子的,所以synchronized只是为了它的通信效果,而不是为了互斥访问。
另外一个解决办法,是在声明stopRequested 时,使用volatile关键字: private static volatile boolean stopRequested = false;
这个关键字不执行互斥访问,但是可以保证任何一个线程在读取该field时,都将看到最近刚刚被写入的值。
在使用volatile时必须要小心。如果你想这样来使用它:
private static volatile int nextSerialNumber = 0;public static int getNextSerialNumber(){ return nextSerialNumber ++;}
这个方法的目的是要确保每个调用都返回不同的值。然而,这个方法不一定会正常工作。
虽然nextSerialNumber 是可原子访问的,但运算符++不是原子性操作,它相当于: nextSerialNumber = nextSerialNumber +1;
所以如果方法不加同步,多次调用方法就有可能返回相同的值。这就是安全性失败。
安全发布
对于类:
public class Holder { private int n; public Holder(int n) { this.n = n; } public void assertSanity() { if(n != n) { throw new AssertionError("This statment is false."); } }}
不安全的发布:
public Holder holder; public void initialize() { holder = new Holder(42); }
如果holder已被初始化,但还没来得及为n初始化为42,其它线程由调用了assertSanity(),而且在两次读取n时,分别读到了0(未初始化) 和 42(已初始化) ,那么就会很悲剧的抛出断言错误。
要安全的发布对象,有几种方法:
- 将对象保存在static field中,作为类初始化的一部分:
public static Holder holder = new Holder(42);
- 将对象引用保存到volatile类型的域或者AtomicReference对象中
- 将对象引用保存到某个正确构造对象的final类型域中
- 将对象保存到某个类型安全的容器中
后记
本条目开始,就进入多线程并发领域了。这块知识与实践也是博主欠缺的,但由于工作所限,现在也只能边学习边做笔记,错误的地方还请批评指正。
本条目关于安全发布的内容,参考了《Java并发编程实战》相关的章节。但博主在测试过程中,并没有发现(n != n) 返回true的情况,JDK和JVM相关信息如下:
java version “1.8.0_111”
Java(TM) SE Runtime Environment (build
1.8.0_111-b14) Java HotSpot(TM) 64-Bit Server VM (build 25.111-b14, mixed mode)
如果有朋友测试发现了这个异常,请联系博主,万分感谢~~
- Effective Java读书笔记二二(Java Tips.Day.22)
- Effective Java读书笔记二(Java Tips.Day.2)
- Effective Java读书笔记二十(Java Tips.Day.20)
- Effective Java读书笔记二一(Java Tips.Day.21)
- Effective Java读书笔记二三(Java Tips.Day.23)
- Effective Java读书笔记二四(Java Tips.Day.24)
- Effective Java读书笔记二五(Java Tips.Day.25)
- Effective Java读书笔记二六(Java Tips.Day.26)
- Effective Java读书笔记二
- effective java读书笔记二
- 《Effective Java》读书笔记二
- Effective java 读书笔记( 二 )
- Effective Java读书笔记一(Java Tips.Day.1)
- Effective Java读书笔记五(Java Tips.Day.5)
- Effective Java读书笔记六(Java Tips.Day.6)
- Effective Java读书笔记八(Java Tips.Day.8)
- Effective Java读书笔记九(Java Tips.Day.9)
- Effective Java读书笔记十(Java Tips.Day.10)
- SpringMVC-Post/Get请求中文乱码问题
- springMVC入门实例 springMVC简单入门
- Myeclipse中断点调试和单步调试
- 机器学习基础——Anaconda环境
- 双硬盘,双显卡,Win10下安装Ubuntu16.04 64bit,GPT分区+UEFI
- Effective Java读书笔记二二(Java Tips.Day.22)
- 求知过程之Ajax表单提交
- java中受检的异常
- MySQL触发器
- HTTP协议
- 幂等性的研究及后台验证短时间内同一申请是否重复提交的方案
- Kotlin学习(二)Hello Kotlin
- 网页中怎样禁止通过输入url直接访问
- C/C++使用openssl进行摘要和加密解密(md5, sha256, des, rsa)