只要存在一个状态变量被一个以上的线程所访问(即被多个函数调用),而且其中的某些线程会写入该变量的情况(即改变该变量的值),便存在着线程安全的隐患,此时,必须使用同步来协调线程该变量的访问。
如果程序忽略了线程的安全,就存在隐患,任何时刻都有崩溃的可能。如果你想修复这些隐患,可以尝试以下3种方法:
a.不要跨线程共享变量
b.使状态变量不可变
c.在任何访问状态变量的地方使用同步
面向对象技术-------封装和数据隐藏,不仅帮助我们编写组织良好的、可维护的类,同时还让我们能更好的实现和维护线程安全。
线程安全:是指一个类在被多个线程访问时,该类总是能正确的运行,不可能存在崩溃的隐患的行为。
(注:下面代码仅仅是做说明使用,不能直接运行)
1.不含状态对象的类永远是线程安全的
@ThreadSafe
public class StatelessFactorizer implements Servlet {
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFormRequest(req); //从ServletRequset中解包数据
BigInteger[] factors = factor(i); //将数据进行因数分解
encodingIntoResponse(resp , factors); //将结果封包到ServletResponse
}
}
StatelessFactorizer类中无状态(变量),也没用引用其他类的变量。一个访问 StatelessFactorizer 的线程,不会影响到访问同一个Servlet的其他线程的计算结果,因为两个线程不共享变量,它们如同访问不同的实例。
2.原子操作影响着线程安全
/** 计算Servlet请求数量 */
@NotThreadSafe
public class UnsafeCountingFactorizer implements Servlet {
private long count = 0; //记录请求数量的变量
public long getCount() { return count; }
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFormRequest(req);
BigInteger[] factors = factor(i);
++count; //每请求一次count自增
encodingIntoResponse(resp , factors);
}
}
UnsafeCountingFactorizer并非线程安全的,尽管它在单线程的环境中运行良好。表面上看++count是一个单独的操作,
但其不是原子操作(作为一个 单独的、不可分割的操作去执行)。于是,++count这么一条语句便是的程序存在了线程安全的隐患。
我们可以设想,同时有A、B两个线程访问 UnsafeCountingFactorizer(假设count初始值为0),则A、B获得的count值都是0,倘若A的操作在B之前完成,则count的值自增成1了,随后B完成操作,问题出来了,B完成后count自增,由于B读取到的count值是0,故B写入count的值为1,覆盖了A写入count中的1。这并不是我们想要的结果,期望中的结果应该是2才对!
粗略的说,原子操作是解决线程安全的主要策略。原子操作能防止多个线程之间对共享变量的写,使得变量出现脏数据的情况。
Java提供了强制原子性的内置锁机制-------synchronized块。
synchronized(lock) {
//访问或修改被锁保护的共享变量
//此块内的所有操作为合成一原子操作,要么全部执行完后释放锁,要么全部不执行
}