不可变对象设计模式

不可变对象设计模式

线程安全性:

所谓共享的资源,是指在多个线程同时对其进行访问的情况下,各线程都会使其发生变化,而线程安全性的主要目的就在于
在受控的并发访问中防止数据发生变化。除了使用synchronized关键字同步对资源的写操作之外,还可以在线程之间不共享
资源状态,甚至将资源的状态设置为不可变。在本章中,我们将讨论如何设计不可变对象,这样就可以不用依赖于synchronized
关键字的约束。

不可变对象的设计:

      无论是synchronized关键字还是显示锁Lock,都会牺牲系统的性能,不可变对象的设计理念在这几年变的越来越受宠,其中Actor模型
(不可变对象在Akka、jActor、Kilim等Actor模型框架中得到了广泛的使用)以及函数式编程语言Clojure等都是依赖于不可变对象的
设计达到lock free(无锁)的。
    Java核心类库中提供了大量的不可变对象范例,其中java.lang.String的每一个方法都没有同步修饰,可是其在多线程访问的情况下是安全的,Java8中通过Stream修饰的ArrayList在函数式方法并行访问的情况下也是线程安全的,所谓不可变对象是没有机会去修改它,每一次的修改都会导致一个新的对象产生,比如String s1="Hello";s1=s1+" world" 两者相加会产生新的字符串。
package MutilThreadModel.NotChangeModel;
import java.util.Arrays;
import java.util.List;

/**
 * Created by JYM on 2019/1/10
 * 有些非线程安全可变对象被不可变机制加以处理之后,照样也具备不可变性,
 * 比如ArrayList生成的stream在多线程的情况下也是线程安全的,同样是因为
 * 其具备不可变性的结果。
 * */

public class ArrayListStream
{
    public static void main(String[] args)
    {
        //定义一个list并且使用Arrays的方式进行初始化
        List<String> list = Arrays.asList("Java","Thread","Concurrency","Scala","Clojure");

        //获取并行的stream,然后通过map函数对list中的数据进行加工,最后输出;
        list.parallelStream().map(String::toUpperCase).forEach(System.out::println);
        list.forEach(System.out::println);
    }
}
/**
 * list虽然是在并行的环境下运行的,但是在stream的每一个操作中都是一个全新的List,
 * 根本不会影响到最原始的list,这样也是符合不可变对象的最基本思想的。
 * */

非线程安全的累加器:

package MutilThreadModel.NotChangeModel;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

/**
 * Created by JYM on 2019/1/10
 * 不可变对象最核心的地方在于不给外部修改共享资源的机会,这样就会避免多线程情况下的数据冲突而导致的数据不一致的情况,
 * 又能避免因为对锁的依赖而带来的性能降低。
 * */

//下面将模仿java.lang.String的方式实现一个不可变的int类型累加器,先来看看不加同步的累加器
public class IntegerAccumulator
{
    private int init;

    //构造时传入初始值
    public IntegerAccumulator(int init)
    {
        this.init = init;
    }

    //对初始值增加i
    public int add(int i)
    {
        this.init += i;
        return this.init;
    }

    //返回当前的初始值
    public int getValue()
    {
        return this.init;
    }

    public static void main(String[] args)
    {
        //定义累加器,并且将设置初始值为0
        IntegerAccumulator accumulator = new IntegerAccumulator(0);

        //定义三个线程,并且分别启动
        IntStream.range(0,3).forEach(i->new Thread(()->
        {
            int inc = 0;
            while (true)
            {
                //首先获得old value
                int oldValue = accumulator.getValue();
                //然后调用add方法计算
                int result = accumulator.add(inc);
                System.out.println(oldValue+"+"+inc+"="+result);
                //经过验证,如果不合理,则输出错误信息
                if (inc+oldValue != result)
                {
                    System.out.println("Error: "+oldValue+"+"+inc+"="+result);
                }

                inc++;
                //模拟延迟
                slowly();
            }
        }).start());
    }
    private static void slowly()
    {
        try{
            TimeUnit.MILLISECONDS.sleep(1);
        }catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}
/**
 * 这段程序即没有对共享资源进行共享锁的保护,也没有进行不可变的设计,在程序的运行过程中偶尔会出现错误的情况。
 * */

方法同步增加线程安全性:

package MutilThreadModel.NotChangeModel;
import DuoThread.BingFaBianCheng.InterruptThreadTest;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

/**
 * Created by JYM on 2019/1/10
 * 方法同步增加线程安全性
 * */

public class IntegerAccumulatorTest
{
    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;
                int result;

                //使用class实例作为同步锁
                synchronized (InterruptThreadTest.class)
                {
                    oldValue = accumulator.getValue();
                    result = accumulator.add(inc);
                }
                System.out.println(oldValue+"+"+inc+"="+result);

                if (inc+oldValue != result)
                {
                    System.out.println("ERROR:"+oldValue+"+"+inc+"="+result);
                }
                inc++;
                slowly();
            }
        }).start());
    }

    private static void slowly()
    {
        try{
            TimeUnit.MILLISECONDS.sleep(1);
        }catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}
/**
 * 这里将数据同步的控制放在了线程的逻辑执行单元中,而在IntegerAccumulator中未增加任何同步
 * 的控制,如果单纯对getValue方法和add方法增加同步控制,虽然保证了单个方法的原子性,但是两个
 * 原子类型的操作在一起未必就是原子性的,因此在线程的逻辑执行单元中增加同步控制是最为合理的。
 * */

不可变的累加器对象设计:

package MutilThreadModel.NotChangeModel;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

/**
 * Created by JYM on 2019/1/10
 * 不可变的累加器对象设计
 * */
//不可变对象不允许被继承
public final class IntegerAccumulator_NoChang
{
    private final int init;

    //构造时传入初始值
    public IntegerAccumulator_NoChang(int init)
    {
        this.init = init;
    }

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

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

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

    public static void main(String[] args)
    {
        //用同样的方式进行测试
        IntegerAccumulator_NoChang accumulator_noChang = new IntegerAccumulator_NoChang(0);
        IntStream.range(0,3).forEach(i->new Thread(()->
        {
            int inc = 0;
            while (true)
            {
                int oldValue = accumulator_noChang.getValue();
                int result = accumulator_noChang.add(inc).getValue();
                System.out.println(oldValue+"+"+inc+"="+result);
                if (inc+oldValue != result)
                {
                    System.out.println("ERROR: "+oldValue+"+"+inc+"="+result);
                }
                inc++;
                slowly();
            }
        }).start());
    }

    private static void slowly()
    {
        try{
            TimeUnit.MILLISECONDS.sleep(1);
        }catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}
/**
 * 重构后的IntegerAccumulator,使用了final修饰其的目的是为了防止由于继承重写而导致失去线程安全性,另外
 * init属性被final修饰不允许线程对其进行改变,在构造函数中赋值后将不会再改变。
 * add方法并未在原有init的基础之上进行累加,而是创建了一个全新的IntegerAccumulator,并未提供任何修改原始
 * IntegerAccumulator的机会,运行上面的程序不会出现ERROR的情况。
 * */

总结:

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

public final class Immutable
{
    private final List<String> list;
    public Immutable(List<String> list)
    {
        this.list = list;
    }
    public List<String> getList()
    {
        return this.list;
    }
}

Immutable类被final修饰因此不允许更改,同样list只能在构造时被指定,但是该类同样是可变的(mutable),因为getList方法返回的list是可被其他线程修改的,如果想要使其真正的不可变,则需要在返回list的时候增加不可修改的约束Collections.unmodifiableList(this.list)或者可控一个全新的list返回

猜你喜欢

转载自blog.csdn.net/leying521/article/details/86249783