顾名思义:单例模式是一种单个实例的设计模式(只能创建一个实例)
该类负责创建自己的对象,同时确保只有单个对象被创建
- 饿汉模式
先把实例提前创建好并初始化,等需要的时候直接调用。
public class Singleton1 {
//构造方法私有化,确保在其他地方不能通过这个构造方法创建实例
private Singleton1(){}
private static Singleton1 instance=new Singleton1();//提前创建一个实例并初始化
//提供一个方法获得已经创建好的实例
public static Singleton1 getInstance(){
return instance;
}
}
注意:(1)构造方法是私有的 (2)提前创建一个私有的实例 (3)对外提供一个方法来获取这个实例 (4)因为获取实例的方法是直接返回这个实例,所以不存在线程安全的问题
- 懒汉模式
当使用到的时候再去进行实例化(先创建但是不进行初始化)
public class Singleton2 {
//构造方法私有化
private Singleton2() {}
//私有化
private static Singleton2 instance = null;
//对外提供方法初始化实例
public static Singleton2 getInstance(){
if(instance==null){
instance=new Singleton2();
}
return instance;
}
}
当对一个共享变量进行写操作的时候,线程是不安全的,要实现一个线程安全的单例模式大家都会想到一种方法就是进行加锁
public static Singleton2 getInstance(){
synchronized (Singleton2.class) {
if (instance == null) {
instance = new Singleton2();
}
}
return instance;
}
加锁确实保证了线程安全,但是只需要第一个线程进行初始化,其他的线程直接拿来使用就行,这样的方式导致每一个线程都要去加锁等待然后判断,效率非常低,我们可以在加锁之前进行判断,如果实例并没有初始化(instance==null),再去进行加锁。
public static Singleton2 getInstance(){
if(instance==null) {
synchronized (Singleton2.class) {
if (instance == null) {
instance = new Singleton2();
}
}
}
return instance;
}
但是到了这一步还是不能达到线程安全!!!!!
再来看看完成代码:
public class Singleton2 {
//构造方法私有化
private Singleton2() {}
//私有化
private static Singleton2 instance = null;
//对外提供方法初始化实例
public static Singleton2 getInstance(){
if(instance==null) {
synchronized (Singleton2.class) {
if (instance == null) {
instance = new Singleton2();
}
}
}
return instance;
}
}
synchronized只能保证原子性和内存可见性,对指令重排序有一定的约束。
对instance的初始化操作并不是一个原子性的操作:1.申请一块内存空间 2.进行实例化3.把引用指向
正常情况下顺序是123,没任何问题,但是在一定的情况下可能会变成132这个时候会导致线程不安全。
比如说线程1申请了一块内存空间,将引用指向,这个时候线程二刚好进行第一个判断发现instance!-null就会直接返回一个instance(这个instance是一个没初始化的错误实例,就会抛异常)。
volatile:对被修饰的变量的操作禁止指令重排序
public class Singleton2 {
//构造方法私有化
private Singleton2() {}
//私有化
private static volatile Singleton2 instance = null;
//对外提供方法初始化实例
public static Singleton2 getInstance(){
if(instance==null) {
synchronized (Singleton2.class) {
if (instance == null) {
instance = new Singleton2();
}
}
}
return instance;
}
}
将instance用volatile进行修饰,禁止指令重排,此时一个线程安全的懒汉模式便写好了。
注意:这里的double-check缺一不可,两个判断的作用不一样。第一个if(instance==null)是为了提高效率,第二个if(instance==null)是为了判断保证只有一个线程去进行共享变量的初始化。