概述
所谓单例设计模式,就是采用一定的方法保证整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个获取其对象实例的方法(静态方法)。
单例模式具体有八种方式:
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式(线程安全,同步方法)
- 懒汉式(线程不安全,同步代码块)
- 双重检查
- 静态内部类
- 枚举
饿汉式(静态常量)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
public class Singleton { private final static Singleton singleton = new Singleton();
private Singleton() {
}
public static Singleton getInstance(){ return singleton; } }
|
饿汉式(静态代码块)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
public class Singleton {
private static Singleton singleton= null;
static { singleton = new Singleton(); }
public static Singleton getInstance(){ return singleton; } }
|
懒汉式(线程不安全)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance(){ if(singleton==null){ singleton = new Singleton(); } return singleton; } }
|
懒汉式(线程安全,同步方法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
public class Singleton { private static Singleton singleton;
private Singleton() { }
public static synchronized Singleton getInstance(){ if(singleton==null){ return singleton = new Singleton(); } return singleton; } }
|
- 解决了线程安全问题
- 效力太低了,每个线程在想获得类得实体时候,执行 getInstance()方法都要进行同步。而其实这个方法只要执行一次实例化就够了,后面的想要获取该类实例,直接return就行了。方法进行同步效率太低。
- 在实际开发中,不建议使用。
懒汉式(线程不安全,同步代码块)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
public class Singleton { private static Singleton singleton;
private Singleton() { }
public static Singleton getInstance(){ if(singleton==null){ synchronized (Singleton.class){ singleton = new Singleton(); } } return singleton; } }
|
- 虽然加了同步关键字,但同样是线程不安全的,因为可能有几个线程同时进入if(singleton == nul)的判断,然后再同步创建出多个实例出来。
双重检查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
public class Singleton { private static volatile Singleton singleton;
private Singleton(){
} public static Singleton getInstance(){
if(singleton==null){ synchronized (Singleton.class){ if (singleton != null) { singleton = new Singleton(); } } } return singleton; } }
|
第一次判断是为了验证是否创建对象,判断为了避免不必要的同步。
第二次判断是为了避免重复创建单例,因为可能会存在多个线程通过了第一次判断在等待锁,来创建新的实例对象。
volatile作用?
- 保证对所有线程的可见性。
- 读volatile:每当子线程某一语句要用到volatile变量时,都会从主线程重新拷贝一份,这样就保证子线程的会跟主线程的一致。
- 写volatile: 每当子线程某一语句要写volatile变量时,都会在读完后同步到主线程去,这样就保证主线程的变量及时更新。
- 禁止指令重排序优化。
- 由于 volatile禁止对象创建时指令之间重排序,所以其他线程不会访问到一个未初始化的对象,从而保证安全性。
为什么要使用volatile关键字?
singleton= new Singleton(); 具体机器码指令执行操作如下:
分配内存地址
对象进行实例化
变量singleton赋值
java代码编程成.class 文件,到最后操作系统执行的时候,会对指令进行优化,进行重排序。会出现1-3-2的情况,这种情况下,对象为实例化情况下, 另一个线程过来后判断对象是否为空,发现对象不为空,直接访问对象,实际上对象还未实例化完毕,生产上就会造成异常。
静态内部类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
public class Singleton {
private static Singleton singleton;
private Singleton(){}
private static Singleton getInstance(){ return SingletonInstance.INSTANCE; }
private static class SingletonInstance{ private static final Singleton INSTANCE = new Singleton(); }
}
|
枚举
1 2 3 4 5 6
|
public enum Singleton { INSTANCE; }
|