前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >多线程设计模式解读5—Immutable Object(不可变对象)模式

多线程设计模式解读5—Immutable Object(不可变对象)模式

作者头像
java达人
发布2018-10-08 11:21:54
6780
发布2018-10-08 11:21:54
举报
文章被收录于专栏:java达人java达人

前面讲了Producer-Consumer模式,它有许多变种,我们以后会讲。我们将接着了解另外一种分支的设计模式,前面所讲的所有的模式,都是要用到锁的,而锁是会带来一些额外的开销和问题的,那么能不能不通过锁,实现多线程环境下的线程安全呢?其中一个思路就是通过Immutable Object(不可变对象)模式。它使用对外可见的不可变对象,天生具有线程安全的“基因”。因为与多线程的原子性、可见性相关的问题(如失效数据、丢失更新操作、对象处于不一致状态等)都与多线程试图同时访问同一个可变状态相关,若对象状态不可变,那这些问题也就不存在了。

不可变对象的条件:

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

构造不可变对象建议:

  • 类声明为final类型,字段可见性设置为private,这样可以防止子类修改其字段值。
  • 字段声明为final字段,这样字段被赋值一次,就不会再被赋值。同时保证了字段引用的对象的初始化安全。
  • 不存在setter方法,且确保字段引用的实例未变化。

示例代码实例如下:

代码语言:javascript
复制
public final class ImmutableCustomer {
    private final String name;
    private final String address;
    public ImmutableCustomer(String name, String address) {
        this.name = name;
        this.address = address;
    }
    public String getName() {
        return name;
    }
    public String getAddress() {
        return address;
    }
    @Override
    public String toString() {
        return "[ ImmutableCustomer: name = " + name + ", address = " + address + " ]";
    }
}

public class OpeCustomerThread extends Thread {
    private ImmutableCustomer immutableCustomer;
    public OpeCustomerThread(ImmutableCustomer person) {
        this.immutableCustomer = person;
    }
    @Override
    public void run() {
        while (true) {
            System.out.println(Thread.currentThread().getName() + "  "
                + immutableCustomer);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        ImmutableCustomer alice = new        ImmutableCustomer("Alice", "Alaska");
        alice = new ImmutableCustomer("Ace", "Alaska");
        new OpeCustomerThread(alice).start();
        new OpeCustomerThread(alice).start();
        new OpeCustomerThread(alice).start();
    }
}

jdk中的CopyOnWriteArrayList也使用了该模式,它是ArrayList的线程安全变体,其中所有变更操作(添加,设置等)都是通过创建底层数组的新副本来实现的(实际上,array的元素是可以被替换的,这是一个事实不可变对象,即对象从技术上而言未满足不可变对象的严格定义,是可变,但其状态在安全发布后不会再改变了)。这需要一定开销,但是当遍历操作远比变更频繁时,它可能比其他方法更有效。它不需要加锁就可以排除并发线程之间的干扰。迭代器不会抛出ConcurrentModificationException。自迭代器创建后,迭代器无需考虑后期修改操作带来的影响。

源码片段如下:

代码语言:javascript
复制
public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private static final long serialVersionUID = 8673264195747942595L;

    /** 锁保护所有的变更操作 */
    final transient ReentrantLock lock = new ReentrantLock();

    /** array,只能通过getArray/setArray访问 */
    private transient volatile Object[] array;

    /**
     * 获取array
     */
    final Object[] getArray() {
        return array;
    }

    /**
     * 设置array.
     */
    final void setArray(Object[] a) {
        array = a;
    }

   /**
     * 添加特定元素到list
     */
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }


    /**
     * 移除特定位置的元素
     */
    public E remove(int index) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            E oldValue = get(elements, index);
            int numMoved = len - index - 1;
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, len - 1));
            else {
                Object[] newElements = new Object[len - 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                setArray(newElements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }


 /**
     * 返回的迭代器提供构造迭代器时list状态的快照。遍历迭代器时不需要同步。 迭代器不支持remove方法。
     */
    public Iterator<E> iterator() {
        return new COWIterator<E>(getArray(), 0);
    }

}

从对以往CopyOnWriteArrayList使用,我们可以总结使用不可变对象模式需要注意的地方:

1、当变更操作比较频繁时,会在状态变化时不断创建替换新的不可变对象,这会加重GC的负担和系统开销,应该谨慎使用。

2、CopyOnWriteArrayList中array的元素是可以被替换的,访问其中的元素需要避免外部代码修改其状态,这里的迭代器不支持remove方法。类似的情况,如我们返回HashMap类型的对象时,需要做好防御性复制:

代码语言:javascript
复制
Collections.unmodifiableMap(deepCopy(map))
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-09-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 java达人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档