Java并发编程——第2章:线程安全性(一)

       要编写线程安全的代码,其核心是对状态访问进行管理,特别是对共享的(Shared)可变的(Mutable)状态进行管理。

对象的状态是指:储存在状态变量中的数据(指可影响任何外部可见行为的数据),还可能包括其他依赖对象的域。

       当多个线程访问一个可变的状态变量时没有使用合适的同步,那么程序就会出现错误。三种方式修正这个错误:

       ① 将可变状态变量转换为不可变的变量

       ② 不在线程之间共享该状态变量

       ③ 在访问状态变量时使用同步

       线程安全的程序并非一定是由线程安全类构成的,线程安全类中也可以包含非线程安全类。

???

 2.1 什么是线程安全性

定义:当多个线程访问某个类时,该类始终表现出正确性,那么该类就是线程安全的。

           在一个线程安全类中实现了必要的同步机制,因此客户端无需进一步采取措施。

       

 无状态的对象一定是线程安全的

无状态:既不包含任何域,也不包含任何对其他类中域的引用。计算过程中的临时状态只能存放于线程栈上的局部变量,并且只能有正在执行的线程访问。

2.2 原子性

++count是一个非原子性操作,它由三个状态“读取-修改-写入”构成,最终的状态由读取的之前的状态决定。

在并发过程中,由于不正确的执行顺序导致不正确的结果的发生的现象叫做:竞态条件

2.2.1 竞态条件

“先检查-后执行”:就是指检查过的结果在执行时已经失效,因为在检查过和执行之间的时间会有新事件发生。

2.2.2 延迟初始化(先检查后执行)

假设两个线程同时调用 getInstance(),会出现竞态条件。

2.2.3 符合操作

       以上例子表明,要避免竞态条件,就必须在某个线程修改变量时,阻止这个操作,防止其他线程使用这个变量,从而确保该访问过程出现在修改前或者修改后,而不是修改中。

      为了确保线程安全性,“先检查,后执行”(延迟初始化)和“读取-修改-删除”(count++)等操作必须是原子的。

         通过AtomicLong 来代替 long类型的计数器,能够确保所有对计数器状态的访问状态都是原子性的。

   在一个无状态的类中添加一个状态时,如果该状态是由线程安全对象管理的,那么该类任然是线程安全的。

   要尽可能用线程安全对象。

2.3 加锁机制

        仿照上例:在Servlet中添加更多的状态是否只需要添加更多的线程安全对象就足够了。

 不正确:

尽管这些原子引用本身是线程安全的,但在此类中存在竞态机制,

2.3 加锁机制

        仿照上例:在Servlet中添加更多的状态是否只需要添加更多的线程安全对象就足够了。

 不正确:

       尽管这些原子引用本身是线程安全的,但在此类中存在竞态机制,因此也不是线程安全的类。

       当不变性条件下涉及多个变量时,各个变量间不是彼此独立的,某个变量的值会约束到其他变量的值。因此,当一个变量发生变化时,需要在一个原子操作中对其他变量进行操作。

        set方法,get方法应保证在number和factor之间的原子性,get要原子内两者都get,set要原子内set。

       要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量。

2.3.1 内置锁

Synchronized (lock){
   //访问或修改由锁保护的共享状态
}

 

 每个Java对象都可以做一个实现同步的锁,这些锁被称为内置锁或者监视器锁。线程在进入同步代码块时会自动获取锁,退出代码块时会自动释放锁。

java中的锁相当于一种互斥体,也就是只有一个线程能拥有这个锁。当A企图获取B持有的锁时,A必须阻塞知道B释放,若B不释放,A将一直阻塞下去。

 

虽然线程安全,但是性能太低,因为同一时刻只能有一个线程执行service

猜你喜欢

转载自blog.csdn.net/Curry7895/article/details/82658836