不可变对象的设计模式

       在多线程操作共享资源的情况下,大多数情况下都需要对资源进行加锁操作,多线程设计模式中的一个方法,可以不依赖加锁操作,直接将资源的状态设置为不可变,一个不可变的对象,无论任何时候都是线程安全的,就像java.lang.String一样。

       那么String类是如何保证线程安全的呢?做过String s1 = “hello”;s1 = s1+”world”;的就会知道,对string类的每一次修改都会产生一个新的对象,这样就不会修改之前的对象数据了。

       另外有些非线程安全可变对象被不可变机制处理后,也是具有了不可变性。比如ArrayList生成的stream在多线程的情况下也是线程安全的,同样是因为其具备不可变性的结果。Stream的每一个操作中都是一个全新的list,不会影响到最原始的list。

public class ArrayListStream {

    public static void main(String[] args) {
        List<String> list = Arrays.asList("java", "thead", "scala", "clojure");
        //获取并行的stream,map函数对list中的数据进行加工,
        list.parallelStream().map(String::toUpperCase).forEach(System.out::println);
        //stream的每一个操作都是一个全新的list,不会影响到最原始的list
        list.forEach(System.out::println);
    }
}

非线程安全的累加器 

import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

/**
 * 非线程安全的累加器
 * 不可变对象核心:不给外部修改共享资源的机会
 */
public class IntegerAccumulator {

    private int init;

    public IntegerAccumulator(int init){
        this.init = init;
    }

    public int add(int i){
        this.init += i;
        return init;
    }

    public int getValue(){
        return this.init;
    }

    public static void main(String[] args) {
        IntegerAccumulator accumulator = new IntegerAccumulator(0);
        IntStream.range(0,3).forEach(i -> new Thread(() ->{
            int inc = 0;
            while (true){
                int oldValue = accumulator.getValue();
                int result = accumulator.add(inc);
                System.out.println(oldValue+"+"+inc+"="+result);
                if(inc + oldValue != result){
                    System.err.println("error:"+oldValue+"+"+inc+"="+result);
                }
                inc ++;
                slowly();
            }
        }).start());
    }

    private static void slowly() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

可能出现的错误:

 方法同步增加线程安全性

public static void main(String[] args) {
        IntegerAccumulator accumulator = new IntegerAccumulator(0);
        IntStream.range(0,3).forEach(i -> new Thread(() ->{
            int inc = 0;
            while (true){
                // TODO: 2019/11/22 加同步锁 实现线程安全
                int oldValue ;
                int result ;
                synchronized (IntegerAccumulator.class){
                     oldValue = accumulator.getValue();
                     result = accumulator.add(inc);
                }
                System.out.println(oldValue+"+"+inc+"="+result);
                if(inc + oldValue != result){
                    System.err.println("error:"+oldValue+"+"+inc+"="+result);
                }
                inc ++;
                slowly();
            }
        }).start());
    }

 这里将同步的控制放在了线程的逻辑执行单元中,而在IntegerAccumulator中未增加任何同步的控制,如果单纯地对getValue 方法和add方法增加同步控制,虽然保证了单个方法的原子性,但是两个原子性的操作在一起未必就是原子性的,因为在线程的逻辑执行单元中增加同步控制是最合理的。

不可变的累加器对象设计 

import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

/**
 * 不可变对象不允许被继承
 */
public final class SafeIntegerAccumulator {

    private final int init;

    public SafeIntegerAccumulator(int init){
        this.init = init;
    }

    //构造新的累加器,需要用到另外一个accumulator和初始值
    public SafeIntegerAccumulator(SafeIntegerAccumulator accumulator,int init){
        this.init = accumulator.getValue() + init;
    }

    //每次相加都会产生一个新的SafeIntegerAccumulator
    public SafeIntegerAccumulator add(int i){
        return new SafeIntegerAccumulator(this,i);
    }

    public int getValue(){
        return this.init;
    }

    public static void main(String[] args) {
        SafeIntegerAccumulator accumulator = new SafeIntegerAccumulator(0);
        IntStream.range(0,3).forEach(i -> new Thread(() ->{
            int inc = 0;
            while (true){
                int oldValue = accumulator.getValue();
                int result = accumulator.add(inc).getValue();
                System.out.println(oldValue+"+"+inc+"="+result);
                if(inc + oldValue != result){
                    System.err.println("error:"+oldValue+"+"+inc+"="+result);
                }
                inc ++;
                slowly();
            }
        }).start());
    }

    private static void slowly() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 重构后的IntegerAccumulator,使用了final修饰,防止由于继承重写而导致失去线程安全性,init属性被final修饰不允许线程对其进行改变,在构造函数中赋值后将不会在改变。Add方法并未在原有init的基础之进行累加,而是创建了一个全新的IntegerAccumulator,并未提过任何修改原始IntegerAccumulator的机会。

总结:

 设计一个不可变的类的共享资源需要具备不可破坏性,比如使用final修饰,另外针对共享资源操作的方法是不允许被重写的,以防止由于继承而带来的安全性问题,但是单凭这两点也不足以保证一个类是不可变的,比如下面的类用final修饰,其中的list也是final修饰,只允许在构造时创建。

import java.util.Collections;
import java.util.List;

public final class Immutable {
    private final List<String> list;

    public Immutable(List<String> list) {
        this.list = list;
    }

    public List<String> getList() {
//        Collections.unmodifiableCollection(this.list);
        return this.list;
    }
}

Immutable类中,getList方法返回的list是可被其他线程修改的,如果想要使其真正的不可变,则需要在返回list的时候增加不可修改的约束Collections.unmodifiableList(this.list)或者克隆一个全新的list返回。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值