并发编程(一):线程安全

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、锁不仅仅是关于同步与互斥的,也是关于内存可见的。为了保证所有的线程都能够看到共享的,可变变量的最新值,读取和写入线程必须使用公共的锁进行同步。

原文地址:https://www.cnblogs.com/yaohuiqin/p/9436390.html