1、编写线程安全的代码,本质上就是管理对状态的访问,而且通常都是共享的、可变的状态。通俗的讲,一个对象的状态就是它的数据,存储在状态变量中,比如实例域或静态域以及其他附属对象的域等等。我们讨论线程安全好像是关于代码的,但是我们真正要做的,是在不可控制的并发访问中保护数据。
无论何时,只要多于一个的线程访问给定的状态变量,而且其中某个线程会写入该变量,此时必须使用同步来协调线程对该变量的访问。
2、没有正确同步的情况下,如果多个线程访问了同一变量,你的程序就存在隐患。有3种方法修复:
不要跨线程共享变量
使状态变量为不可变的
在任何访问状态变量的时候使用同步。
(注:一开始就设计成线程安全的,比后期修复更容易)
3、设计线程安全的类时,优秀的面向对象技术——封装、不可变性以及明确的不变约束——会给线程安全带来诸多帮助。
4、完全由线程安全类构成的程序未必是线程安全的,线程安全程序也可以包含非线程安全的类。
5、类线程安全:当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步以及在调用方法代码不必作其他的协调,这个类的行为仍然是正确的,那么这个类就是线程安全类。
无状态对象用于是线程安全的:
@ThreadSafe
public class StatelessFactorizer implements Servlet{
public void service(ServletRequest req,ServletResponse resp){
BigInterger i=extractfromrequest(req);
.............
}
}
StatelessFactorizer类是无状态类,没有域也没有引用其他类的域。变量是本地变量,这些值存储在线程的栈中,只有执行程序才能访问。两个线程访问StatelessFactorizer,线程之间不共享状态,如图在访问不同的实例。
6、原子性:
i++: 是分三步,读-改-写。
如果不用锁去修复i++的问题:可以调用线程安全类去代替i:
private final AtomicLong count = new AtomicLong(0); public void countadd(){ count.incrementAndGet(); }
当向无状态类中加入唯一的状态元素,而这个状态完全被线程安全的对象所管理,那么新的类仍然是线程安全的。
多个元素状态都是被线程安全的对象所管理,则新的类并不是线程安全的:
UnsafeCachingFactorizer这个类的原子引用时线程安全的,但是其存在竞争关系,导致线程不安全。其有一个不变约束,缓存在lastFactors中的各个因子的乘积应该等于缓存在lastNumber中的数值,只有遵守这个不变约束,我们的Servlet才是正确的。UnsafeCachingFactorizer破坏了这个不变约束,即使每个set调用都是原子的,但是我们无法保证会同时更新lastNumber和lastFactors,当某个线程只修改了一个变量而另一个变量还没有开始修改,其他线程将看到Servlet违反了不变约束。同样的,当线程A尝试获取两个值的时间里,线程B已经修改了它们,线程A会观察到Servlet违反了不变约束。
为了保护状态的一致性,要在单一的原子操作中更新互相关联的状态变量。
竞争条件:想得到正确的答案,要依赖于“幸运”的时序。 最常见的一种竞争条件是:检查再运行(如你观察到文件不存在,然后取创建文件,但事实上,从观察到执行的这段时间,有人已经创建文件,从而引发错误)
检查再运行的常见用法是惰性初始化:延迟对象的初始化,知道程序使用它。
@NotThreadSafe class LazyInitRace{ private ExpensiveObject instance=null; public ExpensiveObject getInstance(){ if (instance == null){ instance = new ExpensiveObject(); } return instance; } }
两个线程同时调用getInstance()会得到不同的结果,然而,我们期望getInstance总是返回相同的实例。
为了避免竞争条件,必须阻止其他线程访问我们正在修改的变量,让我们可以确保:当其他线程想要查看或修改一个状态时,必须在我们的线程开始之前或完成之后,而不能在操作过程中。
假设操作A和操作B,如果从执行A的线程的角度看,当其他线程执行B时,要么B全部执行,要么一点都没有执行,这样A和B互为原子操作。
7、锁不仅仅是关于同步与互斥的,也是关于内存可见的。为了保证所有的线程都能够看到共享的,可变变量的最新值,读取和写入线程必须使用公共的锁进行同步。
,