从今天开始阅读《JAVA并发编程实战》这本书,并记录下自己阅读的记录。
首先感谢该书的原作者,几个外国的大神,也感谢韩锴、方妙两位译者。
程序在各自的进程中运行,相互分离,各自独立执行。由操作系统分配空间。
进程会通过一些原始的机制相互通信:Socket、信号处理、共享内存、信号量和文件。
线程有时被称为轻量级的进程。
同一个进程中的线程访问相同的变量,并从同一个堆中分配对象,但是如果没有机制来同步管理共享数据,就会出现一个线程修改另一个线程正在使用的数据,从而产生意外的结果。
在没有同步的情况下,多线程的各个操作的顺序是不可预测的。
private int value;
public int getNext(){
return value++;
}
上面的方法在单线程时没有问题,如果出现了下面的执行情况。
1,线程A:获得了value,为0。
2,线程B:获得了value,为0。
3,线程A:执行了++,value为1。
4,线程B:执行了++,value为1。
5,线程A:得到了value为1。
6,线程B:得到了value为1。
正确的情况下,线程B获得的value应该是2。但是由于多线程的介入,指令执行的顺序不定,导致了错误的发生。
这里的问题在于,value++看似是一个语句,其实是分为三个步骤执行的,首先获得value的值,然后执行加1的操作,最后写回到value上。
这就是常见的并发危险:竞争条件(race condition)。当被多线程访问时,上面的方法能否返回不同的值,取决于CPU调度的顺序,这是我们不希望看到的。
允许多线程访问和修改相同的变量,给顺序编程模型引入了一些非顺序的因素。
简单的处理方法是,使用synchronized来修饰上面的方法,保证访问的唯一性。
在不使用同步的时候,编译器、硬件和运行时事实上对时间和活动的顺序是很随意的。
如果安全意味着什么坏事都没有发生过,活跃度关注是好事最终发生了。当一个活动进入了某种它永远无法在继续执行的状态时,活跃度失败就发生了。顺序程序中,粗心造成的死循环就是一种活跃度失败。
通过框架来访问应用程序中的组件,组件需要访问程序的状态,因此要求在所有的代码路径上,必须是线程安全的。
Timer用来调度一些稍后运行的任务,TimerTasks运行在由Timer管理的线程中,如果TimerTasks访问了其他应用程序正在访问的数据,那么不仅TimerTask需要线程安全,并且那些被同时访问的数据也要相应的保护措施。
编写线程安全的代码,本质上就是管理对状态的访问,而且通常是共享的、可变的状态。
所谓共享就是一个变量可以被多个线程访问,所谓可变就是变量的值在他的生命周期内是可以改变的。所以我们要在不可控制的并发中保护数据的安全性。
Java的首要同步机制就是synchronized关键字,他提供了独占锁。
如果多个线程访问同一个变量,有3种方法修复
1,不要跨线程共享变量。
2,使得状态变量为不可变。
3,在任何访问状态变量的时候使用同步。
一开始将一个类设计成线程安全的,比后期重新修复他更容易。
设计线程安全的类时,优秀的面向对象技术——封装、不可变性以及明确的不变约束——会给你提供诸多的帮助。
首先让你的代码正确,然后在让他跑的更快,总是一个良好的实践。
一个线程安全程序是完全由线程安全类构成的吗?不必要——完全由线程安全类构成的程序也未必是线程安全的。