java并发与高并发学习系列3:安全发布对象
网站首页 文章专栏 java并发与高并发学习系列3:安全发布对象
java并发与高并发学习系列3:安全发布对象
编辑时间:2019-05-06 18:07 作者:毛毛小妖 浏览量:88 评论数:0

一、发布对象与对象溢出

发布对象:使一个对象能够被当前范围之外的代码所使用,例如通过方法返回对象的引用,或者通过公有的静态变量发布对象
对象溢出:一种错误的发布,即发布了本不该发布的对象。当一个对象还没有构造完成时,就被其他线程所见。

1、对象的发布有如下几种方式: 

a、将对象的引用保存在公有变量或公有静态变量中

public class Test {
	public static List<People> list;
	public Test(){
		list = new ArrayList<People>();
	}
}

通过list对象可以轻易的遍历list对象中保存的People对象,实际上list对象中保存的People对象也被间接的发布了。
一个已经发布的对象能够通过非私有的变量引用或方法调用到达其他的对象,那么这些对象也会被发布。

b、在一个非私有的方法中返回该引用

public class Test {
	private String[] strs = {"AB","BA"};
 
	public String[] getStrs() {
		return strs;
	}
}

通过getStrs()方法可以获得本来应该被封装在Test类内部的strs数组

c、将对象引用传递给外部方法

外部方法:对当前类来说,外部方法是指行为不完全由当前类来规定的方法,包括其他类中定义的方法以及当前类中可以被改写的方法(既不是私有方法,也不是final方法)
当把一个对象传递给外部方法时,就相当于发布了这个对象

public class Test {
	public void get(Object obj){ //obj对象逸出
		...
	}
}

2、this引用逸出

a、内部类中this引用逸出
在内部类中保存了一个指向创建该内部类的外围类的引用,所以内部类中可以使用创建该内部类的外围类的私有属性、方法

public class Outer {	
	private String str="Outer's string";
	
	public class Inner{
		public void write(){
			System.out.println(Outer.this.str);
		}
	}
	
	public static void main(String[] args) {
		Outer out = new Outer();
		Outer.Inner in = out.new Inner();
		in.write();
	}
}

执行后的结果是:Outer's string。当Inner类被发布的同时,Outer类也会被发布

b、构造函数中this引用逸出

public class ThisEscape {
	private Thread t;
	public ThisEscape(){
		System.out.println(this);
		t = new Thread(){
			public void run(){
				System.out.println(ThisEscape.this);
			}
		};
		t.start();
		// do something
	}
	
	public static void main(String[] args){
		ThisEscape a = new ThisEscape();
	}
}

运行结果:

ThisEscape@33de22
ThisEscape@33de22

this引用被线程t共享,故线程t的发布将导致ThisEscape对象的发布,由于ThisEscape对象被发布时还未构造完成,这将导致ThisEscape对象逸出(在构造函数中创建线程是可以的,但是不要在构造函数执行完之前启动线程)
在构造函数中调用一个可改写的实例方法时,也会导致同样的问题
解决方式(私有构造函数+公有工厂方法):

public class ThisEscape {
	private final Thread t;
	
	private ThisEscape(){
		t = new Thread(){
			public void run(){
				// do something
			}
		};
		// do something
	}
	
	public static ThisEscape getInstance(){
		ThisEscape thisEscape = new ThisEscape();
		thisEscape.t.start();
		return thisEscape;
	}
}

二、安全发布四种方法

1、在静态初始化函数中初始化一个对象引用
2、将对象的引用保存到volatile类型域或AtomicReference对象中
3、将对象的引用保存到某个正确构造对象的final类型域中
4、将对象的引用保存在一个由锁保护的域中

三、安全发布对象栗子

1、将对象的引用保存到volatile类型域

/**
 * 懒汉模式 -》 双重同步锁单例模式
 * 单例实例在第一次使用时进行创建
 */
@ThreadSafe
public class SingletonExample5 {

    // 私有构造函数
    private SingletonExample5() {

    }

    // 1、memory = allocate() 分配对象的内存空间
    // 2、ctorInstance() 初始化对象
    // 3、instance = memory 设置instance指向刚分配的内存

    // 单例对象 volatile + 双重检测机制 -> 禁止指令重排
    private volatile static SingletonExample5 instance = null;

    // 静态的工厂方法
    public static SingletonExample5 getInstance() {
        if (instance == null) { // 双重检测机制        // B
            synchronized (SingletonExample5.class) { // 同步锁
                if (instance == null) {
                    instance = new SingletonExample5(); // A - 3
                }
            }
        }
        return instance;
    }
}

2、在静态初始化函数中初始化一个对象引用

/**
 * 饿汉模式
 * 单例实例在类装载时进行创建
 */
@ThreadSafe
public class SingletonExample6 {

    // 私有构造函数
    private SingletonExample6() {

    }

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

    static {
        instance = new SingletonExample6();
    }

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

    public static void main(String[] args) {
        System.out.println(getInstance().hashCode());
        System.out.println(getInstance().hashCode());
    }
}

 

推荐文章
来说两句吧
最新评论
    还没有人评论哦,快来坐沙发吧!