使用volatile类型发布不可变对象 ( 转 )

22 篇文章 2 订阅

原文地址:https://blog.csdn.net/neuxq/article/details/52896083

最近在看《java并发编程实践》这本书,看到关于不可变对象的介绍,以前并没有接触过,感觉不错。在这里介绍一下。

volatile简介

volatile 是 java 的一种削弱的同步,volatile 的功能只是能够保证对于变量修改时能够保证立即写入内存。被声明的变量能够保证可见性,但是并不能够满足原子性,整个过程中还是可以同时被其他的线程改变。所以 volatile 使用有一定的局限性,对于 volatile 的详细介绍可以参考一下这里,我就不班门弄斧了,这里主要介绍一下一种新学习的处理同步的方法:不可变对象。

不可变对象

如果某个对象在被创建后其状态就不能被修改,那么这个对象就称为不可变对象

当满足以下条件时,对象才是不可变的:

  • 对象创建以后其状态就不能修改。
  • 对象的所有域都是 final 类型。
  • 对象时正确创建的(在对象的创建期间,this引用没有逸出)。

这里需要注意的是对于 final 的引用,需要其所指的对象一样是不可变的。例如在 TreeStooges 类中的所有成员在创建对象时就不可变了,所以是一个不可变类。

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);
    }
}

使用volatile类型发布一个不可变对象

一个不可变对象一定是线程安全的,所以对于不可变对象处理同步的提供了一个新的思路,之前经常考虑对共享变量的同步,现在可以通过不可变对象完成同步。

如下例子:

public class CachedFactorizer extends GenericServlet implements Servlet {
    private BigInteger lastNumber;
    private BigInteger[] lastFactors;
    private long hits;
    private long cacheHits;

    public synchronized long getHits() {
        return hits;
    }

    public synchronized double getCacheHitRatio() {
        return (double) cacheHits / (double) hits;
    }

    public void service(ServletRequest req, ServletResponse resp) {
        BigInteger i = extractFromRequest(req);//通过request获得请求的整数i
        BigInteger[] factors = null;
        synchronized (this) {
            ++hits;
            if (i.equals(lastNumber)) {
                ++cacheHits;
                factors = lastFactors.clone();//如果有缓存,直接获取结果
            }
        }
        if (factors == null) {
            factors = factor(i);
            synchronized (this) {
                lastNumber = i;
                lastFactors = factors.clone();//对该整数i进行因式分解
            }
        }
        encodeIntoResponse(resp, factors);//返回response
    }
}

这里 CachedFactorizer 类是 Servlet 请求,对整数 i 进行因式分解,并且缓存了上一次请求的数据,如果两次请求相同,那么可以直接返回结果。由于对于缓存的结果不同的请求线程都会做相应的更改,所以需要对缓存 lastNumberlastFactors 采用 sychronized 同步,保证每一个线程都只能单一处理缓存。

修改成不可变对象如下:

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);
    }
}

建立不可变对象 OneValueCache,用于缓存数据。注意 Arrays.copyOf() ,对于数组的拷贝,如果直接引用数组的话,OneValueCache 就不是不可变对象了。

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);//通过request获得请求的整数i
        BigInteger[] factors = cache.getFactors(i);//对该整数i进行因式分解
        if (factors == null) {
            factors = factor(i);
            cache = new OneValueCache(i, factors);//对于不同的请求生成不同的缓存
        }
        encodeIntoResponse(resp, factors);//返回response
    }
}

对于不同的请求生成不同不可变对象缓存,对于其他线程请求只能访问不能修改。如果有新的缓存,则将其替换掉,这里 cache 变量需要声明为 volatile类型,保证其可见性。在 java 的并发类 CopyOnWriteArrayListCopyOnWriteArraySet就是使用这个思想,即“写入时复制”。有兴趣可以看其源码。

总结

这种方式比较于使用 sychronized 同步机制有很强的并发性,volatile 本来就是 sychronized 的削弱机制,需要的额外消耗较少,所以这种方式的同步效果较好。但是一般情况下,只有在遍历比修改频繁的时候才会考虑用这种方式,主要原因是如果数据庞大,频繁的申请和回收得不偿失。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值