单例设计模式

197 阅读2分钟

定义:某个类在整个系统中只能有一个实例对象可被获取和使用的代码模式。

实例:JVM中的Runtime类

要点实现方法
某个类只能有一个实例构造器私有化
必须自行创建这个实例含有一个该类的静态变量来保存这个唯一的实例
必须自行向整个系统提供这个实例对外提供获取该实例对象的方式

实现方法

1.饿汉式:直接创建对象,不存在线程安全问题

  • 直接实例化
  • 枚举式
  • 静态代码块

2.懒汉式:延迟创建对象

  • 线程不安全(适用于单线程)
  • 线程安全(适用于多线程)
  • 静态内部类形式(适用于多线程)

饿汉式

1.直接实例化

public class Singleton1{
    public static final Singleton1 INATANCE= new Singleton1();
    private Singleton1(){
        
    }
}

2.枚举式

原理:枚举类的构造器是私有的。

public enum Singleton2{
    INSTANCE;
}

3.静态代码块

public class Singleton3{
    public static final Singleton3 INSTANCE;
    static{
        INSTANCE=new Singleton3();
    }
    private Singleton3(){
        
    }
}

优势:相比于直接实例化,静态代码块可以通过修改配置文件类修改实体类

具体例子

public class Singleton3{
    public static final Singleton3 INSTANCE;
    private String info;
    static{
    	//从配置文件中读取信息,并且给info赋值
        INSTANCE=new Singleton3(info);
    }
    private Singleton3(String info){
        this.info=info;
    }
}

懒汉式

方法一

public class Singleton4{
    private static Singleton4 instance;
    private Singleton4(){
        
    }
    public static Singleton4 getInstance(){
        if(instance==null){
           	//标记1
            instance = new Singleton4();
        }
        return instance;
    }
}

缺点:线程不安全

产生原因,先复现一下bug

单线程的情况

public class TestSingleton4{
    public static void main(String[] args) {
    	Singleton4 s1=Singleton4.getInstance();
    	Singleton4 s2=Singleton4.getInstance();
    	System.out.println(s1==s2);
    }
}

打印结果 true

多线程的情况

public class TestSingleton4{
    public static void main(String[] args) {
        Callable<Singleton4> c = new Callable<Singleton4>(){
            @Override
            public Singleton4 call throws Exception{
                return Singleton4.getInstance();
            }
        }
        ExecutorService es =Executors.newFixedThreadPool(2);
        Future<Singleton4> f1=es.submit(c);
        Future<Singleton4> f2=es.submit(c);
        
        Singleton4 s1=f1.get();
        Singleton4 s2=f2.get();
        
    	System.out.println(s1==s2);
    }
}

有可能会打印false。Ps:可以在标记1出加个延时,这样出现false的概率很高。

原因分析:第一个线程判断instance==null,为true,进入了if代码块,此时如果会出现线程阻塞的情况,在第二个线程进行instance==null判断时,第一个线程还未创建好实例对象,所以也进行实例的创建,相当于进行了两次实例对象的创建。

解决方法:加锁(synchronized)

public class Singleton5{
    private static Singleton5 instance;
    private Singleton5(){
        
    }
    public static Singleton5 getInstance(){
        synchronized(Singleton5.class){
        	if(instance==null){
           		//标记1
            	instance = new Singleton4();
        	}
        }
       	return instance;
    }
}

缺点:每次都会等待其它线程解锁

优化:双重检验锁

public class Singleton5{
    private volitale static Singleton5 instance;
    private Singleton5(){
        
    }
    public static Singleton5 getInstance(){
        if(instance==null){
        	synchronized(Singleton5.class){
        		if(instance==null){
           			//标记1
            		instance = new Singleton4();
        		}
        	}
        }
       	return instance;
    }
}

方法二:静态内部类

原理:静态内部类不会自动随着外部类的加载和初始化而初始化,它是需要单独去加载和初始化的。

public class Singleton6{
    private Singleton6(){
        
    }
    private static class Inner{
        private static final Singleton6 INSTANCE = new Singleton6();
    }
    public static Singleton6 getInstance(){
        return Inner.Instance;
    }
}