zoukankan      html  css  js  c++  java
  • Java

    这篇记录一下保证并发安全性的策略之——不变性。
    (注意:是Immutable,不是Invariant!)


    将一连串行为组织为一个原子操作以保证不变性条件,或者使用同步机制保证可见性,以防止读到失效数据或者对象变为不一致状态,这些问题都是因为共享了可变的数据。

    如果我们能保证数据不可变,则这些复杂的问题就自然不用去考虑了。

    不可变对象一定是线程安全的。


    说简单也简单,不可变对象只有一种状态,且由构造器控制。

    因此,判断不可变对象的状态变得特别简单。

    当我们共享一个可变对象,其状态的改变行为都是难以预料的,尤其是作为参数传给了可覆盖的方法时,更糟糕的是这些client代码都可以保留该对象的引用,也就是说状态改变的时机也同样难以预料。

    相对于可变对象的共享,不可变对象的共享则简单很多,而且几乎不用考虑弄一个快照。

    于是我们现在有了一个新的问题:如何让状态不可变?


    对于"不可变"这一说法无论是JLS还是什么地方都没有明确的定义,但不可变绝对不仅仅是加个final修饰那么简单,比如final修饰的field引用的是一个可变对象,而final保证的仅仅是引用的指向不会发生变化。

    没错,不可变对象和不可变的对象引用是两码事


    对于如何构建一个不可变对象,我们有三个条件(虽然说是"条件",但并不是那么硬性的,可以算是某种建议):

    1. 对象创建后保证状态不可变

    2. 对象的所有field都是final

    3. 创建期间没有逸出自身引用,保证对象的创建正确。


    关于上面三条,这里举一个例子:

     public final class ThreeStooges {
        private final Set<String> stooges = new HashSet<String>();
    
        public ThreeStooges() {
            stooges.add("Moe");
            stooges.add("Larry");
            stooges.add("Curly");
        }
    
        public boolean isStooge(String name) {
            return stooges.contains(name);
        }
    
        public String getStoogeNames() {
            List<String> stooges = new Vector<String>();
            stooges.add("Moe");
            stooges.add("Larry");
            stooges.add("Curly");
            return stooges.toString();
        }
    }
    


    让我们检查一下是否满足三个条件:

    1. 对象创建后保证状态不可变,是否有变化? 我们首先是用private修饰了stooges,接着提供的两个公有方法中第一个方法是返回boolean而第二个方法getStoogeNames中我们重新创建了一个stooges且保证相同的逻辑而不是直接引用stooges field。

    2. 对象的所有field都是final,很明显,我们用了final进行描述以防止对象状态在对象生命周期内改变其引用。

    3. 创建期间没有逸出自身引用,在stooges声明时我们就指定了引用,并在构造函数中将其初始化,不会有外来方法可以引用到该状态并将其改编。


    不得不说这个final修饰是关键。

    通常我们对final关键字最直观的印象是,如果一个用final修饰的对象引用的指向是不会改变的(发现这话怎么说都很难表达清楚,但是你懂的),但即使引用了可变的实例,就判断状态而言,加了final就可以简化不少,分析基本不可变的对象总比分析完全可变的对象来得容易多了吧....

    而final和synchronized关键字那样也有多个语义,就是——能确保初始化过程的安全性,从而可以自由共享,不需要进行同步处理(这个同步处理不包括可见性)。


    下面是一段用final(更确切地说应该是不可变性)保证了操作原子性(以保证可变性条件)一段例子。

    某个Servlet接收参数后将参数传入factor方法对其进行运算并将结果进行响应。

    假设这个factor方法非常耗时,于是我们想出了一个方法暂时缓解这一状况,即下一次请求的参数和上一次请求的参数相同则响应缓存中的结果。

    也就是说每一次请求时我们多了一个步骤,也就是需要判断请求的数字是否和缓存中的一样,如果不同则重新计算,而这一段并不是原子操作,并发出现时会出现破坏可变性条件的情况。

    而为了应对这个问题,我们可以将这一部分用synchronized保证其原子性,但这里使用另一种方式,使用不可变对象:

    public class OneValueCache {
        private final BigInteger lastNumber;
        private final BigInteger[] lastFactors;
    
        public OneValueCache(BigInteger i,
                             BigInteger[] factors) {
            lastNumber = i;
            lastFactors = Arrays.copyOf(factors, factors.length);
        }
    
        public BigInteger[] getFactors(BigInteger i) {
            if (lastNumber == null || !lastNumber.equals(i))
                return null;
            else
                return Arrays.copyOf(lastFactors, lastFactors.length);
        }
    }
    


    这个不可变对象是如何设计的?

    首先我们保证了所有状态用final进行修饰并在唯一的构造器中进行初始化,注意构造器中对lastFactors进行初始化的那一段,我们用Arrays.copyOf保证了其正确构造,也就是防止逸出。

    然后是唯一一个公有方法,这个方法要返回的正是我们计算好的factors,但我们不能直接返回factors,也是为了防止逸出,我们使用了Arrays.copyOf。

    下面是使用缓存的Servlet,整个对象只有一个field就是cache,我们用volatile修饰以保证并发时的可见性,即线程A改变了引用时线程B可以立即看到新的缓存。

    public class VolatileCachedFactorizer extends GenericServlet implements Servlet {
        private volatile OneValueCache cache = new OneValueCache(null, null);
    
        public void service(ServletRequest req, ServletResponse resp) {
            BigInteger i = extractFromRequest(req);
            BigInteger[] factors = cache.getFactors(i);
            if (factors == null) {
                factors = factor(i);
                cache = new OneValueCache(i, factors);
            }
            encodeIntoResponse(resp, factors);
        }
    
        void encodeIntoResponse(ServletResponse resp, BigInteger[] factors) {
        }
    
        BigInteger extractFromRequest(ServletRequest req) {
            return new BigInteger("7");
        }
    
        BigInteger[] factor(BigInteger i) {
            return new BigInteger[]{i};
        }
    }
  • 相关阅读:
    小白学 Python 数据分析(21):pyecharts 好玩的图表(系列终篇)
    小白学 Python 数据分析(20):pyecharts 概述
    小白学 Python 数据分析(19):Matplotlib(四)常用图表(下)
    小白学 Python 数据分析(18):Matplotlib(三)常用图表(上)
    在 ASP.NET Core 程序启动前运行你的代码
    在 ASP.NET Core 项目中使用 MediatR 实现中介者模式
    在 ASP.NET Core 项目中使用 AutoMapper 进行实体映射
    [Vue 牛刀小试]:第十七章
    或许是你应该了解的一些 ASP.NET Core Web API 使用小技巧
    [Vue 牛刀小试]:第十六章
  • 原文地址:https://www.cnblogs.com/kavlez/p/4041395.html
Copyright © 2011-2022 走看看