Java并发编程之对象的发布与逸出

2018-06-17  本文已影响0人  embers1996

概念

发布:使对象能够在当前作用域之外的代码中使用
对象逸出:一种错误的发布,当一个对象还没构建完成时,就被其他线程所见。

不安全发布对象

import com.gwf.concurrency.annoations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;

public class UnsafePublish {
    private String[] states = {"a","b","c"};

    /**
     * 通过public发布级别发布了类的域,在类的外部,任何线程都可以访问这个域
     * 这样是不安全的,因为我们无法检查其他线程是否会修改这个域导致了错误
      * @return
     */
    public String[] getStates() {
        return states;
    }

    public static void main(String[] args) {
        UnsafePublish unsafePublish = new UnsafePublish();
        log.info("{}", Arrays.toString(unsafePublish.getStates()));

        unsafePublish.getStates()[0] = "d";
        log.info("{}", Arrays.toString(unsafePublish.getStates()));

    }
}

对象溢出

public class Escape {

    private int thisCannBeEscape = 0;

    public Escape(){
        new InnerClass();
    }
    private class  InnerClass{
        public InnerClass(){
            log.info("{}",Escape.this.thisCannBeEscape);
        }
    }

    public static void main(String[] args) {
        new Escape();
    }
}

安全发布的方法

1.懒汉式

public class SingletonExample {
    //私有构造函数
    private SingletonExample(){
    }

    //单例对象
    private static SingletonExample instance = null;

    //静态工厂方法
    public static SingletonExample getInstance(){
        if(instance==null){
            return new SingletonExample();
        }
        return instance;
    }
}

在多线程环境下,当两个线程同时访问这个方法,当两个线程都执行到if(instance==null)时,都判断为null,接下来同时执行new操作。这样类的构造函数被执行了两次。一旦构造函数中涉及到某些资源的处理,那么就会发生错误,懒汉式是线程不安全的,所以一般不建议使用这种方式。

2.懒汉式(synchronized)

在类的静态方法上使用synchronized修饰

 public static synchronized SingletonExample getInstance()

使用synchronized修饰静态方法后,保证了方法的线程安全性,同一时间只有一个线程访问该方法 ,但是会有性能损耗。

3.懒汉式(双重同步锁单例模式 )

public class SingletonExample {
    // 私有构造函数
    private SingletonExample() {
    }
    // 单例对象
    private static SingletonExample instance = null;
    // 静态的工厂方法
    public static SingletonExample getInstance() {
        if (instance == null) { // 双重检测机制
            synchronized (SingletonExample.class) { // 同步锁
                if (instance == null) {
                    instance = new SingletonExample();
                }
            }
        }
        return instance;
    }
}

这里有一个知识点:CPU指令相关
在上述代码中,执行new操作的时候,CPU一共进行了三次指令
(1)memory = allocate() 分配对象的内存空间
(2)ctorInstance() 初始化对象
(3)instance = memory 设置instance指向刚分配的内存

在程序运行过程中,CPU为提高运算速度会做出违背代码原有顺序的优化。我们称之为乱序执行优化或者说是指令重排。
那么上面知识点中的三步指令极有可能被优化为(1)(3)(2)的顺序。当我们有两个线程A与B,A线程遵从132的顺序,经过了两此instance的空值判断后,执行了new操作,并且cpu在某一瞬间刚结束指令(3),并且还没有执行指令(2)。而在此时线程B恰巧在进行第一次的instance空值判断,由于线程A执行完(3)指令,为instance分配了内存,线程B判断instance不为空,直接执行return,返回了instance,这样就出现了错误。
解决方法:
在对象声明时使用volatile关键字修饰,阻止CPU的指令重排。

private volatile static SingletonExample instance = null; 

4.饿汉式

public class SingletonExample {
    // 私有构造函数
    private SingletonExample() {

    }
    // 单例对象
    private static SingletonExample instance = new SingletonExample();

    // 静态的工厂方法
    public static SingletonExample getInstance() {
        return instance;
    }
}

5.饿汉式(静态块初始化)

public class SingletonExample {
    // 私有构造函数
    private SingletonExample() {
    }
    // 静态域
    private static SingletonExample instance = null;
    //静态块
    static {
        instance = new SingletonExample();
    }
    // 静态的工厂方法
    public static SingletonExample getInstance() {
        return instance;
    }
    public static void main(String[] args) {
        System.out.println(getInstance().hashCode());
        System.out.println(getInstance().hashCode());
    }
}

6.枚举式

public class SingletonExample {

    private SingletonExample() {
    }

    public static SingletonExample getInstance() {
        return Singleton.INSTANCE.getInstance();
    }

    private enum Singleton {
        INSTANCE;
        private SingletonExample singleton;

        Singleton() {
            singleton = new SingletonExample();
        }

        public SingletonExample getInstance() {
            return singleton;
        }
    }
}
上一篇 下一篇

猜你喜欢

热点阅读