写并发程序时, 很多问题前人已经终结了有模板套的编程模式,遇到相似的问题时套用模式无需从头重新思考实现.避免bug的产生,以下罗列了并发编程常用的一些模式与使用场景
  • 不变模式

多线程环境下, 能final的尽量final修饰. 这点可以配合"写时复制"一起理解,如copyOnWriteArray写时并没有改动原先的数组(也就是不可变),
修饰成final并不是就意味着线程安全,因为只是引用不可变,对象属性还是多线程修改的. 同时还要注意 "不安全发布" 的问题(如方法返回保护对象引用)

  • 线程封闭

即共享变量只有持有的线程这一个线程可以访问,不让共享,也就线程安全了. 这就是线程封闭的含义. 对应于java中,指的就是ThreadLocal

  • Guarded Suspension模式

翻译过来可以是"有保证的中止", 总觉得怪怪的,其实说的就是 多线程的 等待通知模型. 如下异步转同步的场景就用到这种模式

public class DefaultFuture<T> {
    /**
     * 结果对象
     */
    private T obj;
    private final Lock lock = new ReentrantLock();
    private final Condition done = lock.newCondition();
    private final int timeout = 2;

    final static Map<Object, DefaultFuture> fs = new ConcurrentHashMap<>();

    /**
     * 静态方法创建GuardedObject(DefaultFuture)
     * @param key
     * @param <K>
     * @return
     */
    public static <K> DefaultFuture create(K key) {
        DefaultFuture f = new DefaultFuture();
        fs.put(key, f);
        return f;
    }

    public static <K, T> void fireEvent(K key, T obj) {
        DefaultFuture go = fs.remove(key);
        if (go != null) {
            go.onChanged(obj);
        }
    }

    /**
     * 返回结果对象
     */
    public T get(Predicate<T> p) {
        lock.lock();
        try {
            //MESA管程推荐写法
            while (!p.test(obj)) {
                done.await(timeout, TimeUnit.SECONDS);
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }

        return obj;
    }


    public void onChanged(T obj) {
        lock.lock();
        try {
            this.obj = obj;
            done.signalAll();
        } finally {
            lock.unlock();
        }
    }

}
  • COW, 即copy on write 模式

适用于 维护数据量少, 读多写少的场景. 如rpc调用里面的路由信息维护, 元数据维护, 缓存等场景.
注意: 因为写时复制,先copy,copy完后,然后调整引用指向. 故读时会有短暂的不一致. 用到的时候需要考虑到场景是否能够容忍这种不一致现象.

  • balking 模式

与其说是一种模式,还不如说是对"同步"概念的进一步思考. 如果多线程场景含有"条件依赖"的语义,(如单例初始化,如果没有初始化才初始化)
这就没必要将整个实例化方法加锁. 只需要对实例化方法块加锁. 如下所示相比于对整个getInstance方法加锁,已经实例化的情况性能会更高.


class Singleton{
  private static volatile  Singleton singleton;
  //构造方法私有化  
  private Singleton() {}
  //获取实例(单例)
  public static Singleton  getInstance() {
    //第一次检查
    if(singleton==null){
      synchronize(Singleton.class){
        //获取锁后二次检查
        if(singleton==null){
          singleton=new Singleton();
        }
      }
    }
    return singleton;
  }
}
  • Work per Thread

即每个任务一个线程,java中的线程属于特别消耗占资源的对象,用原生的不可行. 轻量级线程(协程)可采用此模式,java开源项目 Loom 项目的 Fiber
据说可以实现,有空可以研究下

  • Work-Thread 模式

也就是线程池模式.需要注意的是线程池里的任务每个任务需要各自独立,避免有依赖关系,不然的话有死锁风险.
出现这种问题的解决办法是,将互相有依赖的线程放在不同的池中执行.

  • 两阶段终止协议(thread.interrupt)

java中的中断并不是粗鲁的一刀切中断, 而是通过中断标识符,将中断后的处理逻辑交给需要被中断的线程来处理

  1. 抛出interruptException后,当前线程的中断标识会被清除. 故捕捉到异常后,需要重新设置异常标识(Thread.currentThread().interrupt())
  2. 如果处理中断的线程中引用了第三方包,可能第三方包没有正确处理中断异常(如未重新设置中断标识),这种情况下最好自己新建一个

中断状态,线程的中断依赖于自己建的这个状态,而非线程自带的.

  • 生产者-消费者模式.

也是最多运用的一种多线程协同模式,用的最多, 不做展开


niguanwo
1 声望0 粉丝

这里当做笔记本.