单例模式,最常见的有两种单例模式,饿汉式和懒汉式,如下:
/**
* 饿汉式
*/
public class SingletonHungry {
//单例对象
private SingletonHungry instance = new SingletonHungry();
//私有构造方法
private SingletonHungry(){}
public SingletonHungry getInstance(){
return instance;
}
}
/**
*懒汉式
*/
public class SingletonLazy {
//单例对象
private static SingletonLazy instance;
//私有构造方法
private SingletonLazy(){}
//静态工厂方法
public static SingletonLazy getInstance(){
if(instance==null){
instance = new SingletonLazy();
}
return instance;
}
}
上述两种方式都存在着线程不安全的情况,以懒汉式为例,instance对象还没创建的时候,如果两条线程访问getInstance方法,进入了if条件,同时都new的话,就产生了两个实例。
解决方案:synchronized加双重检测
/**
*synchronized加双重检测
*/
public class SingletonSyn {
//单例对象
private static SingletonSyn instance;
//私有构造函数
private SingletonSyn(){}
//静态工厂方法
public static SingletonSyn getInstance(){
//双重检测
if(instance == null){
synchronized (SingletonSyn.class){
if(instance==null){
instance = new SingletonSyn();
}
}
}
return instance;
}
}
解析:双重检测的好处,就是首先判断instance为空,有两条线程都可以进入了这判断条件,当一条线程执行完了getInstance方法,释放了锁, 然后另一条线程已经进入第一层判断,这时可获取锁,如果在synchronized代码块不做判断就有产生了一个实例,就不是单例了
存在问题:此时仍难不是线程安全的,因为JVM会进行指令重排,new一个对象的步骤是1.分配对象的内存空间2.初始化对象3.设置instance指向刚分配的实例 *有些时候JVM会把其顺序条为1,3,2,3过程执行了对象就不等于null,所以就存在下边这种情况,线程A在new对象,线程B在第一个条件判断 * 当线程A先执行了1,3,此时线程B刚好判断为false,那么线程B直接返回instance,这是一个没创建好的对象,所以就出错了
解决方案:单例加上volatile关键字防止指令重排
/**
*线程安全的单例代码
*/
public class SingletonSyn {
//单例对象
private volatile static SingletonSyn instance;
//私有构造函数
private SingletonSyn(){}
//静态工厂方法
public static SingletonSyn getInstance(){
//双重检测
if(instance == null){
synchronized (SingletonSyn.class){
if(instance==null){
instance = new SingletonSyn();
}
}
}
return instance;
}
}
存在问题:此时还会存在一个问题,我们能利用反射仍可获得多个对象
解决办法:用枚举类
/**
* 枚举类实现单例模式
*/
public enum SingletonEnum {
INSTANCE;
private SingletonEnum() {
}
public void print(){
System.out.println("hello world");
}
}
public static void main(String[] args) {
SingletonEnum instance = SingletonEnum.INSTANCE;
instance.print();
}
至此单例一些情况全都介绍完了