zoukankan      html  css  js  c++  java
  • 并发编程设计模式--Immutability模式

    不变性(Immutability)模式

      所谓不变性,简单来讲,就是对象一旦被创建之后,状态就不再发生变化。换句话说,就是变量一旦被赋值,就不允许修改了(没有写操作);没有修改操作,也就是保持了不变性。

    一、如何让一个类具有不变性呢

      将一个类所有的属性都设置成 final 的,并且只允许存在只读方法,那么这个类基本上就具备不可变性了。更严格的做法是这个类本身也是 final 的,也就是不允许继承。因为子类可以覆盖父类的方法,有可能改变不可变性,所以推荐你在实际工作中,使用这种更严格的做法。实际上在Java SDK 经常用到的 String 和 Long、Integer、Double 等基础类型的包装类都具备不可变性。通过看源码,可以发现他们的类和属性都是 final 的,所有方法均是只读的

    你或许有疑问,String类里面有很多方法是可以修改字符串的。看过底层源码的小伙伴一定知道,String的SubString()  repleace()方法 都是会新创建对象的。

     

     所有的修改操作都创建一个新的不可变对象,创建的对象太多了,太浪费内存,有没有什么办法可以解决呢?

    二、利用享元模式可以减少创建对象的数量

    如果你熟悉面向对象相关的设计模式,相信你一定能想到享元模式(Flyweight Pattern)。利用享元模式可以减少创建对象的数量,从而减少内存占用。Java 语言里面 Long、Integer、Short、Byte 等这些基本数据类型的包装类都用到了享元模式。

    以下是Long中源码,Long 内部维护了一个静态的对象池,仅缓存了 [-128,127] 之间的数字,这个对象池在 JVM 启动的时候就创建好了。 因为 Long 这个对象的状态共有 264 种,实在太多,不宜全部缓存,而 [-128,127] 之间的数字利用率最高

    在Integer中也是一样的

    面试曾经被问到,有个疑问,“Integer 和 String 类型等等 基础类型的包装类是适合做锁” ?不能,因为它们内部用到了享元模式,这会导致看上去私有的锁,其实是共有的。切记

     

    三、使用 Immutability 模式的注意事项:

    1. 对象的所有属性都是 final 的,并不能保证不可变性;
    2. 不可变对象也需要正确发布。

    在下面的代码中,Foo 具备不可变性,线程安全,但是类 Bar 并不是线程安全的,类 Bar 中持有对 Foo 的引用 foo,对 foo 这个引用的修改在多线程中并不能保证可见性和原子性。

     1 //Foo 线程安全
     2 final class Foo{
     3   final int age=0;
     4   final int name="abc";
     5 
     6 }
     7 
     8 //Bar 线程不安全
     9 class Bar {
    10   Foo foo;
    11   void setFoo(Foo f){
    12     this.foo=f;
    13   }
    14 
    15 }

    也就是说:不可变对象虽然是线程安全的,但是并不意味着引用这些不可变对象的对象就是线程安全的

    如果你的程序仅仅需要 foo 保持可见性,无需保证原子性,那么可以将 foo 声明为 volatile 变量,这样就能保证可见性。如果你的程序需要保证原子性,那么可以通过原子类AtomicReference来实现,AtomicReference也是java.util.concurrent包下的类,跟AtomicInteger等是一样的,也是基于CAS无锁理论实现的,但是不同的是 AtomicReference 是操控多个属性的原子性的并发类。

    来上源码,这里需要注意下,这里的比对两个对象,比对的方式不是equals而是==,意味着比对的是内存的中地址,这个我们可以通过unsafe.compareAndSwapObject()方法查看,是一个native方法,调用计算机底层硬件实现的。

     1 public class SafeWM {
     2     //库存变化对象的上限、下限
     3   class WMRange{
     4     final int upper;
     5     final int lower;
     6     WMRange(int upper,int lower){
     7     // 省略构造函数实现
     8     }
     9 
    10   }
    11   //构造封装的原子类引用对象
    12   final AtomicReference<WMRange>rf = new AtomicReference<>( new WMRange(0,0) );
    13 
    14   // 只设置库存上限
    15   void setUpper(int v){
    16     while(true){
    17        WMRange old = rf.get();
    18       // 检查参数合法性
    19       if(v < or.lower){
    20          throw new IllegalArgumentException();
    21       }
    22 
    23       WMRange new = new WMRange(v, old.lower);
    24      //cas关键方法
    25       if(rf.compareAndSet(old, new)){
    26       
    27          return;
    28 
    29       }
    30 
    31     }
    32 
    33   }
    34 
    35 }    

    总结

    利用 Immutability 模式解决并发问题,也许你觉得有点陌生,其实你天天都在享受它的战果。Java 语言里面的 String 和 Long、Integer、Double 等基础类型的包装类都具备不可变性,这些对象的线程安全性都是靠不可变性来保证的。Immutability 模式是最简单的解决并发问题的方法,建议当你试图解决一个并发问题时,可以首先尝试一下 Immutability 模式,看是否能够快速解决。




    ==========================================================================           如果您觉得这篇文章对你有帮助,可以【关注我】或者【点赞】,希望我们一起在架构的路上,并肩齐行
    ==========================================================================
  • 相关阅读:
    yk20192320
    JS常用方法 限制用户输入的方法
    话说最强悍的团队,博客园团队!
    输入框打开禁用自动填充功能
    老生常谈:Asp.net MVC 3+ Jquery UI Autocomplete实现百度效果
    节日logo
    第一篇:Asp.net MVP模式介绍
    VBS 类
    《JavaScript编程精解》简明读书心得上
    Three.js源码阅读笔记1
  • 原文地址:https://www.cnblogs.com/amberJava/p/12446010.html
Copyright © 2011-2022 走看看