安全发布对象
发布与溢出
发布对象:使一个对象能够被当前范围之外的代码所使用
对象溢出:一个错误的发布。当一个对象没有构建完成时,就使他被其它线程所见
发布代码演示
public class PublishExample1 {
private String[] status={"a","b","c"};
public String[] getStatus(){
return status;
}
public static void main(String[] args) {
PublishExample1 example1=new PublishExample1();
System.out.println(Arrays.toString(example1.getStatus()));
example1.getStatus()[0]="d";
System.out.println(Arrays.toString(example1.getStatus()));
}
}
注:通过代码的访问级别发布了外部的域,在类的任何外部的线程都可以访问这些域,这样的发布对象其实是不安全的,无法保证其它线程会不会修改这些域
溢出代码演示
public class PublishExample2 {
private int count=0;
public PublishExample2(){
new Inner();
}
private class Inner{
public Inner(){
System.out.println("count:"+PublishExample2.this.count);
}
}
public static void main(String[] args) {
new PublishExample2();
}
}
注:在对象没有正确构造完成之前就被发布,容易造成一些不安全因素,这里可以采用工厂方法、私有构造函数来完成对象创建和构造器的注册,这里的目的是对象未完成注册之前不可以发布对象
安全发布对象—四种方法
在静态初始化函数中初始化一个函数的引用,非线程安全
/**
* 懒汉模式
* 第一次加载时初始化
*/
public class SingletonExample1 {
private SingletonExample1(){}
private static SingletonExample1 example1=null;
public static SingletonExample1 getInstance(){
if(example1==null){
example1=new SingletonExample1();
}
return example1;
}
}
注:非线程安全
/**
* 饿汉模式
* 类装载使用时被创建
*/
public class SingletonExample2 {
private SingletonExample2(){}
private static SingletonExample2 example1=new SingletonExample2();
public static SingletonExample2 getInstance(){
return example1;
}
}
注:如果构造方法中存在过多的处理,会造成类加载的时候特别的慢,引起性能问题,如果使用饿汉模式只使用类的加载没有实质使用会造成资源浪费
/**
* 饿汉模式--静态快
* 类装载使用时被创建
*/
public class SingletonExample6 {
private SingletonExample6(){}
private static SingletonExample6 example1=null;
static {
example1=new SingletonExample6();
}
public static SingletonExample6 getInstance(){
return example1;
}
public static void main(String[] args) {
System.out.println(getInstance().hashCode());
System.out.println(getInstance().hashCode());
}
}
注:静态块中初始化,注意example1定义和static块的位置,private static SingletonExample6 example1=null;放在static下方会报空指针
/**
* 枚举实现单例模式---线程安全,推荐使用
* 类装载使用时被创建
*/
public class SingletonExample7 {
private SingletonExample7(){}
public static SingletonExample7 getInstance(){
return Singleton.INTEGEN.getInstance();
}
private enum Singleton{
INTEGEN;
private SingletonExample7 example7=null;
private Singleton(){
example7=new SingletonExample7();
}
public SingletonExample7 getInstance(){
return example7;
}
}
public static void main(String[] args) {
System.out.println(getInstance().hashCode());
System.out.println(getInstance().hashCode());
}
}
注:绝对安全,枚举中构造方法时JVM保证该方法绝对只被执行一次,推荐这种方式
将对象的引用保存到volatile类型或着AtomicReference对象中
/**
* 懒汉模式---双重校验锁,使用volatile优化
* 第一次加载时初始化
*/
public class SingletonExample5 {
private SingletonExample5(){}
private static volatile SingletonExample5 example1=null;
public static SingletonExample5 getInstance(){
if(example1==null){
synchronized (SingletonExample5.class){//同步锁
if(example1==null){//双重检测
example1=new SingletonExample5();
}
}
}
return example1;
}
}
注:优化双重校验锁,线程安全
将对象的引用保存到某个正确构造对象的final类型域中
将对象的引用保存到一个由锁保护的域中
/**
* 懒汉模式
* 第一次加载时初始化
*/
public class SingletonExample1 {
private SingletonExample1(){}
private static SingletonExample1 example1=null;
public static synchronized SingletonExample1 getInstance(){
if(example1==null){
example1=new SingletonExample1();
}
return example1;
}
}
注:线程安全但不推荐使用,被synchronize修饰,同一时刻只允许一个线程访问,影响性能
/**
* 懒汉模式---双重校验锁
* 第一次加载时初始化
*/
public class SingletonExample4 {
private SingletonExample4(){}
private static SingletonExample4 example1=null;
public static SingletonExample4 getInstance(){
if(example1==null){
synchronized (SingletonExample4.class){//同步锁
if(example1==null){//双重检测
example1=new SingletonExample4();
}
}
}
return example1;
}
}
注:双重检验锁单例模式,但是并非线程安全的,当一个线程访问后发现synchronize进行实例化后,第二个线程在访问发现已经实例化,就不会往下继续执行这些都没有问题,问题出在,分三个步骤,1、分配对象内存空间,2、初始化对象。3、设置指向刚分配的内存,在多线程环境下会指令重排,如JVM和cpu优化发生了指令重排,上面的3个步骤发生了顺序上的变化