在构建稳健的并发程序时,除了正确的使用线程和锁外。如何对状态访问进行管理是编码的核心。特别是对共享的(shared)和可变的(Mutable)状态的访问。“共享”意味着变量可以由多个线程同时访问,而“可变”则意味着变量的值在其生命周期内可以发生变化。我们讨论线程安全性更侧重于如何防止在数据上发生不受控的并发访问。
一个对象是否需要是线程安全的,取决于它是否被多个线程访问。这是程序中访问对象的方式,而不是对象要实现的功能。我们可以通过同步机制来协同对对象可变状态的访问来使其线程安全。当无法实现协同,那么可能导致数据破坏甚至程序出现错误。有三种方式可以修复这个问题:
1、不在线程之间共享该状态变量。
2、将状态变量修改为不可变的变量。
3、在访问状态变量时使用同步。
在我们设计类与程序时应该考虑并发访问的情况,以免后期采用上述方法对设计进行重大修改。这一切都是为了保障线程安全性。
那什么是线程安全性呢?
线程安全性核心概念是正确性,正确性的含义是某个类的行为与其规范完全一致。我们根据实际的场景可以总结出:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。在线程安全类中封装了必要的同步机制,因此客户端无须进一步采取同步措施。
例子:基于Servlet的因数分解服务,并通过扩展其功能,同时确保它的线程安全性
public class StatelessFactorizer implements Servlet{ public void service(ServletRequest request, ServletResponse response){ BigInteger i = extractFormRequest(request); BigInteger[] factors = factor(i); encodeIntoResponse(response,factors); } }
该类是无状态的,不包含任何域,也不包含任何对其他类中域的引用,计算过程中的临时状态仅存于线程栈上的局部变量中,并且只能由正在执行的线程访问。访问该类的线程不会影响另一个访问同一个类的线程的计算结果。两个线程之间没有共享状态。线程访问无状态对象的行为并不会影响其他线程中操作的正确性,因此无状态对象一定是线程安全的。大多数的Servlet都是无状态的。
由非原子性操作来了解何为竞态条件:
何为原子性:一个或多个操作,要么全部执行完成并且执行过程不被打断,要么不执行。我们用简单的例子在说明并发编程中的原子性:
假定有两个操作A和B,如果从执行A的线程来看,当另一个线程执行B时,要么将B全部执行完,要么完全不执行B,那么A和B对彼此来说是原子的。此处我们只是复习一下概念不多做讲解。
在编码时我们常常遇见一个典型的非原子性操作
private long count = 0; public long getCount(){return count}; public void service(ServletRequest request, ServletResponse response){ ++count; }
看上去这个只是一个操作,实际上包含了三个独立操作。读取count的值,将值+1,然后将结果写入count。这是一个典型的“读取-修改-写入”的操作。当两个线程在没有同步的情况下去对一个count进行递增操作。我们想要的正常结果应该是2,但某些情况下,每个线程读到的值都是0,递增后都将count设为1。导致这种特殊情况的原因是不恰当的执行时序,我们称之为”竞态条件“。当某个计算的正确性取决于多个线程的交替执行时序时,就会发生竞态条件。其本质是基于一种可能失效的观察结果来做出判断或者执行某个计算,最常见的竞态条件类型就是”先检查后执行“。
后续的慢慢更新,目前是边读并发编程实战,边更新博客。本人文字功底不好,大部分都是书中所讲,我只是改动部分说法来尽量衔接书中讲解的知识。对并发编程有兴趣的朋友,个人强推并发编程实战。哈哈
原文地址:https://www.cnblogs.com/zhangbLearn/p/9630952.html