Java并发编程学习记录#1

来源:互联网 发布:最全合理网络理财产品 编辑:程序博客网 时间:2024/05/18 03:28

线程安全篇A

其实,并发编程理论并不过多的涉及线程和锁,虽然构建并发程序需要正确的使用线程和锁,然而这只是内部机理带来的手段而已;本质上来说,写出线程安全的程序,在于状态的访问管理,特别是共享和可变状态。

一个对象的状态是其数据,它可以存储在其实例或者静态域中,这些数据也可以来自其它独立的实例对象。对象的状态,包括了那些所有会影响其外部可见行为的数据。

  • 说到共享,意思是一个变量可以被多个线程访问;
  • 说到可变性,意思是一个值可以在其生命周期内被改变。

我们谈到线程安全似乎指的是代码,但真正我们要做的是,保护数据不被不受控制的访问影响。

对象是否需要设置成线程安全,在于其是否需要多线程访问,程序如何使用对象,决定了其是否需要线程安全,而不取决于这个对象能做什么。

Java中同步的一个基本机理是使用synchronized关键字,这能提供一个排它锁;同时,同步也包含了volatile关键字,具体锁以及原子变量。不遵从以上规则的看似稳定的情况,总是存在一定的隐患。

多线程访问可变状态时,若没有适合的同步操作,总是存在问题隐患。有三个基本方式可以修复此类问题。

  • 不要在线程间共享可变状态
  • 将状态设置成不可变
  • 无论何时访问可变状态,都使用同步

设计一个线程安全的类要远比将类重构成线程安全容易的多。

面向对象编程的一些技巧可以帮助更容易的创建线程安全类,比如封装和数据隐藏。越少代码访问特定的变量,越容易将其同步,越好的封装,越容易实现线程安全。

什么是线程安全

线程安全类的定义:当被多线程访问时,它总能表现正确,而不管运行环境下多线程的调度或是线程的交叉执行,并且不需要调用代码进行额外的同步或其它协调操作。线程安全类封装了一切需要的同步,因此客户端并不需要自己提供这些。

无状态的对象总是线程安全的,比如绝大多数的Servlet对象(Javaweb,例子省略),它并没有成员变量,也没有其它类成员的引用,它涉及的计算只存在于局部变量中,而局部变量是线程私有的,所以其它线程的操作并不会影响当前线程的计算或状态,即它是线程安全的。

原子性

原子操作:单一的,不可被拆分的操作,这是相对所有操作而言的,其总是操作在同一个状态上。
比如

int i = 0;++i;//op1

op1并不是一个原子操作,它是三个子操作的序列:获取数据,修改数据,将数据写回。
这是一个读-改-写的操作,在某些场景下,不同步这种操作会引发线程不安全的问题,如serlvet中统计访问数。

  1. 竞争条件

竞争条件的出现:当计算结果的正确性依赖于运行时的相对时间点或是线程的交叉调用。
最常见的竞争条件类型:检查-执行策略。这可能造成过期的检查结果决定下一步的行为。

  • 懒加载中的竞争条件

懒加载指的是直到真正使用一个对象时,才去初始化它,并只初始化一次。未做同步处理的懒加载容易出现线程安全问题。比如下面这个例子。

@NotThreadSafepublic class LazyInitRace {    private ExpensiveObject instance = null;    public ExpensiveObject getInstance() {        if (instance == null)            instance = new ExpensiveObject();        return instance;    }}class ExpensiveObject { }

首先,getInstance方法将检查ExpensiveObject对象是否已经存在,若是的话,直接返回实例对象,若否的话,则创建一个实例返回。但是这里面是会受到竞争条件影响的。比如,加入有A和B两个线程同时访问getInstance,A发现ExpensiveObject对象为null,于是开始创建它,此时B也在检查该对象是否为空,而这个结果是难以预测的,比如线程如何调度,初始化占用的时间以及给成员instance赋值的时机等。而B检查的结果,又会带来getInstance方法返回不同的结果。

数字自增操作和对象懒加载操作都是一系列操作的序列,这会造成竞争条件的发生。因此,为了避免竞争条件的发生,这些操作需要一种方式来避免一个正在修改的状态被其它线程访问,即需要保证,当要访问或修改某个状态时,可以发生在这个状态修改前或是修改后,而不是在修改中。

其中的一种方式,就是将读-改-写(比如自增操作)以及检查-执行(比如懒加载)的操作变为原子性的。比如自增操作,可以使用concurrent包中的原子型类AtomicLong。比如懒加载,可以使用加锁同步的方式使其能够被原子性的执行。

//待下篇

主要参考自_ Java Concurrency in Practice

原创粉丝点击