美文网首页Java 并发编程
Java并发编程笔记(三):组合模式

Java并发编程笔记(三):组合模式

作者: yeonon | 来源:发表于2018-06-28 12:22 被阅读2次

    就和设计模式一样,并发的开发中也有一些组合模式来实现安全的并发,使用这些模式对开发线程安全的程序大有裨益。

    一、设计线程安全的类

    在设计线程安全类的过程中,需要包含以下三个基本要素:

    • 找出构成对象状态的所有变量。
    • 找出约束状态变量的不变性条件。
    • 建立对象的并发访问管理策略。

    上面的描述太抽象了,下面来一一分析。

    找出构成对象状态的所有变量

    对象的状态大多数时候指的是字段(域),例如User类有id,name,age等字段,这些都是一个user对象的状态,找出这些状态并分析哪些状态是可变的,哪些状态是不可变的,之后的工作就是控制那些可变的,从而保证线程安全。所以找出对象的状态是设计线程安全的类的准备工作。

    找出约束状态变量的不变性条件

    首先先看看不变性条件的定义,在维基百科上给出:在计算机科学中,不变条件是指,在程序执行过程或部分过程中,可始终被假定成立的条件。比如,循环不变条件是指在循环开始和结束后始终成立的条件。

    例如要枚举表示范围的元组集合,无论程序是如何执行的,都要保证元组的下界小于等于上界,这就是不变性条件。

    那为什么要找出不变性的条件呢?我的理解是:多线程环境下的程序最麻烦的就是线程执行的顺序不可预测,也就导致了状态的变化不可预测,如果想要实现线程安全,那么就必须要找到某个可以约束状态的条件,而且这个条件在整个程序的执行过程中或者部分执行过程中是不变的,否则这个约束条件就没有意义。

    建立对象的并发访问管理策略

    如何建立并发访问策略呢?经过前两个步骤,应该已经明确了要保护哪些状态以及约束状态的不变性条件,那么现在就开始选择不同的方法来做具体实现了。并发访问管理策略有很多,J.U.C包下的很多类库,synchronized内置锁等等,具体使用那种策略还是得具体场景具体分析,没有最好的,只有最适合的。

    二、并发访问的策略

    1. 实例封闭

    类似于线程封闭,简单来说就是对象被封闭在特点的作用域中,只能通过特定的路径才能被访问到,这样只要在路径上做一些同步处理,那么这个对象就是线程安全的。如下代码:

    public class UserSafe {
        
        private Set<User> userSet = new HashSet<>();
        
        public synchronized boolean isContain(User user) {
            return userSet.contains(user);
        }
        
        public synchronized void addUser(User user) {
            userSet.add(user);
        }
    }
    

    User的状态由HashSet来管理,HashSet本身不是线程安全的,但是因为userSet是UserSafe私有的域,所以并不会暴露,因此可以说User被封闭在UserSafe中,唯一访问到userSet的路径是代码中的那两个方法,现在只需要对这两个方法采取同步操作,那么这个类就是线程安全的类了。

    这里还有一个问题是,如果想要提供获取User的方法,该如何处理?简单的加上synchronized 并不能解决问题,因为外部拿到user对象之后仍然可能对user的状态做修改,即user对象逸出了,这时候就无法保证线程安全了。故需要对User类本身做更多的处理,即保证User对象本身是线程安全的。

    2. 线程安全性的委托

    将几个线程安全的类组合在一起构成一个类,这个类就一定是线程安全的吗?答案是:“视情况而定”。即有可能是,有可能不是,所以要进行进一步的分析判断是否需要做更多的同步处理。委托是一种比较方便且简洁的实现方式,将类扔给一些能够保证线程安全的容器,对该类的操作首先经过容器,这样就可以保证类的线程安全了。例如新构造了一个类A,现在想要保证A的线程安全,一种实现方法是使用ConcurrentHashMap等线程安全的容器来包装,也可以使用AtomicReference来包装,具体使用哪种,取决于需求。

    总之,这是一种比较简单直接的方式,可能对性能有一些影响,但是降低了编写代码的难度,算是一种折中吧,不过需要注意的是有可能委托失败,这种情况可能得需要做进一步分析,来确定最终的实现方案了。

    3.使用Java类库中的基础模块

    其实和委托有差不多的意思,例如Collections里有很多synchronized为前缀的方法,这些方法接受容器类,然后返回一个线程安全的类,查看源码可知大多数实现都是简单粗暴的在方法上加入synchronized关键字,这样可能会造成一些性能损失。不过也不失为一种方法。

    总结

    实现线程安全是一件很困难的事,幸运的是前辈们替我们铺好了路,提出了很多模式,解决方案,使得我们可以快速的实现线程安全,提高生产效率。

    水平有限,如文章中描述有错误或者不准确的地方,望多多指正。

    相关文章

      网友评论

        本文标题:Java并发编程笔记(三):组合模式

        本文链接:https://www.haomeiwen.com/subject/zjxgyftx.html