java并发编程实战阅读笔记(第四章)对象的组合

来源:互联网 发布:淘宝上的装修付款方式 编辑:程序博客网 时间:2024/05/22 16:38

一、设计线程安全的类

在设计线程安全类的过程中,需要包含三个步骤:
1)找出构成对象状态的所有变量。
2)找出约束状态变量的不变形条件。
3)建立对象状态的并发访问管理策略。

对象的域:是指对象中的变量。
对象的状态:如果对象中的所有域都是基本类型的变量,那么这些域将构成对象的全部状态。如果对象中引用了其他对象,那么该对象的状态将包含被引用对象的域。

收集同步需求

如果不了解对象的不变性条件与后验条件,那么就不能确保线程安全性。要满足在状态变量的有效值或状态转换上的各种约束条件,就需要借助原子性和封装性。
说的更简略些是Java线程安全问题都是因为共享变量,共享变量后会因为多个线程同时修改导致不正确的问题,所以收集一共有多少处会涉及到这些需要同步的变量,只有收集说有可能出问题的因素基于此之上保证所有元素线程安全也才能保证程序是线程安全的。

依赖状态的操作

在某些对象的方法中还包含一些基于状态的先验条件,如删除前的非空判断,这样的操作就被称为依赖状态的操作。要想实现某个等待先验条件为真时才执行的操作,一种简单的方法是通过现有库中的类,如阻塞队列Blocking Queue或者信号量Semaphore来实现依赖状态的行为。

状态的所有权

单独一个基本对象比较保证其安全性,但是如果是包含对象的集合(容器类 例如:ArrayList),容器类通常表现出一种“所有权分离”的形式。
即使用线程安全的容器类(Collections.synchronizedList(List)),也只能保证容器相关的操作是线程安全的,如果发布了可变对象的引用,就不会拥有独占的控制权。(非线程安全)

二、实例封闭

将数据封装在对象内部,可以将数据的访问限制在对象的方法上,从而更容易确保线程在访问数据时总能持有正确的锁。
封闭机制更易于构造线程安全的类,因为当封闭类的状态时,在分析类的线程安全性时就无须检查整个程序。
Java监听器模式
遵循java监视器模式的对象会把对象的所有可变状态都封装起来,并由对象自己的内置锁来保护。
示例:

public class Counter {    private long value = 0;    public synchronized long getValue(){        return this.value;    }    public synchronized long increment(){        if(value == Long.MAX_VALUE){            throw new IllegalStateException("counter overflow");        }        return ++value;    }}

或者:

public class Counter {    private Long value = 0l;    public long getValue(){        synchronized (value) {            return this.value;        }    }    public long increment(){        synchronized (value) {            if(value == Long.MAX_VALUE){                throw new IllegalStateException("counter overflow");            }            return ++value;        }    }}

使用私有的锁对象而不是对象的内置锁,有许多优点,私有的锁对象可以将锁封装起来,使客户端代码无法获取到锁,以便参与到它的同步策略中,避免产生活跃性问题。

synchronized为什么不能修饰基本数据类型?
因为基本数据类型是放在栈里面的,栈数据是可共享的,所以不能加synchronized。

三、线程安全性的委托

通过多个线程安全的类组成的类不一定是线程安全的
1、委托给单个线程安全状态变量可以保证线程安全性
2、委托给多个相互独立的线程安全状态变量可以保证线程安全性(完全独立,不存在依赖状态的操作)
3、如果类包含多个线程安全状态变量的符合操作,则无法保证线程安全性,可以通过加锁机制保证
4、安全发布底层状态的线程安全类
concurrentHashMap
copyOnWriteArrayList

四、现有的线程安全类中添加功能

1、客户端加锁机制
  使用某个对象的代码时必须使用该对象本身用于保护其状态的锁,不推荐(同步的实现被分到两个不相关的类中)
在客户端加锁,但要确保使加锁对象在实现客户端加锁或者外部加锁时使用同一个锁。

@ThreadSafe  public class ListHelper<E>{      public List<E> list = Collections.synchronizedList(new ArrayList<E>());      public boolean putIfAbsent(E x){          synchronized(list){//使List在实现客户端加锁或者外部加锁时使用同一个锁              boolean absent = !list.contains(x);              if(absent)                  list.add(x);              return absent;          }      }  }  

另外一个更好的方式是使用组合

@ThreadSafe  public class ImprovedList<T> implements List<T>{      private final List<T> list;      public ImprovedList(List<T> list){          this.list = list;      }      public synchronized boolean putIfAbsent(T x){          boolean contains = list.contains(x);          if(!contains)              list.add(x);          return !contains;      }  }  

将同步策略文档化

在文档中说明客户代码需要了解的线程安全性保证,以及代码维护人员需要了解的同步策略。

0 0