单例

  单例模式(singleton)指的是一个类只有一个对象,其好处在于节约资源,如数据库连接池。

1、饿汉式

public class hungry {
    private static hungry instance = new hungry();
    public static hungry getInstance(){
        return instance;
    }
}

  利用类加载的时候回执行所以static修饰的字段这一特性,在类第一次加载的时候初始化instance字段。加载类的时候就初始化好了instance这种行为被称为饿汉。缺点是非懒加载即可能在并不需要该对象的时候就加载了该对象。

2、懒汉-线程不安全

public class lazy_nosafe {
    private static lazy_nosafe instance;
    public lazy_nosafe getInstance(){
        if(instance == null){
            instance = new lazy_nosafe();
        }
        
        return instance;
    }
}

  只有在第一次调用getInstance方法是时候才初始化对象,这种懒惰的行为被称为懒汉。缺点是线程不安全,可能存在A B两个线程同时通过instance==null的条件,这样会新建两个instance不符合单例的要求。

 

3、懒汉-线程安全

public class lazy_safe {
    private static lazy_safe instance;
    public static synchronized lazy_safe  getInstance(){
        if (instance == null){
            instance = new lazy_safe();
        }
        
        return instance;
    }
}

  对创建单例的代码进行加锁避免两个线程同时进入创建单例的临界区,虽然有一定的性能损耗但也保证了线程安全。

 

4、双重检查

public class doubleCheck {
    private static volatile doubleCheck instance;
    public static doubleCheck getInstance(){
        if(instance == null){
            synchronized (doubleCheck.class){
                instance = new doubleCheck();
            }
        }
        return instance;
    }
}

  线程安全懒汉一个缺点是锁的范围太大了,如果一个线程只想获取单例的对象该线程也需要获得锁。尽管有些地方把这种方式成为丑陋的方法因为在1.5以下的版本该方法失效,但在1.5以上的版本该方法是一种兼顾并发安全和效率的方法。

  为了解决锁的范围太大的问题,这里只把锁加在了instance==null即需要新建对象的地方,当然这种优化需要volatile关键字来禁止重排序,具体的来说是禁止对new doubleCheck的重排序。new doubleCheck由三个可重排序的操作组成:1、在堆上分配内存M 2、把M的引用赋给instance 3、初始化instance 事实上再第二步把M的地址赋给Instance的时候instance已经!=null了,虽然这个时候还没有完成对instance的初始化即instance还没有指向一个有意义的对象。所以在发生重排序的时候如果步骤2先执行导致instance!=null,此时B线程获得处理机,B线程判断insance!=null直接返回instance,此时Instance其实上一个空的引用,会出发空指针异常。

  

 

  当用volatile修饰instance之后禁止了这种重排序从而避免了这种情况的发生。

 

posted @ 2019-03-27 21:37  AshOfTime  阅读(219)  评论(0编辑  收藏  举报