什么是单例模式
单例模式属于设计模式中的创建型,顾名思义就是用来创建对象,而且是用来创建单个对象,并且每次获取的对象都是同一个。
单例模式的实现
懒汉式
懒汉?不得不说这个名字有点清新脱俗而又一针见血,从字面上看就是这个人很懒,结合我们的编程就是说当我们需要这个对象的时候才会去获得这个对象,代码如下:
//懒汉模式
public class Singleton {
private static Singleton singleton;
//将构造器私有
private Singleton(){
}
public static Singleton getInstance(){
if (singleton==null)
return new Singleton();
else
return singleton;
}
}
实现懒汉式还有一种静态内部类的方法
public class Singleton4 {
private Singleton4(){
}
private static class SingletCreat{
private static Singleton4 instance=new Singleton4();
}
public static Singleton4 getInstance(){
return SingletCreat.instance;
}
}
懒汉式实现了对象的延迟获取也保证了对象的单一性,但是却还不够安全,因为这样的逻辑结构在单线程中可能是安全的,但是在多线程中,还是不可避免的造成对象实例化的错误,所以我们需要加锁来避免,下面会讲到如何加锁。
饿汉式
同懒汉相比,饿汉就没有那么文雅了,他是一上来就直接new一个对象,毕竟先得到才是有保障的,就像谈恋爱,一定要先下手为强,咳咳,走远了。看代码
//饿汉模式
public class Singleton2 {
//一开始就创建好对象
private static Singleton2 singleton2=new Singleton2();
//构造器私有化
private Singleton2(){
}
public static Singleton2 getInstance(){
return singleton2;
}
}
因为一开始就创建好了对象,所以饿汉式天生就是线程安全的。
双重加锁
之前有说过懒汉式是不安全的,我们可以通过加锁来解决这个问题
//双重加锁
public class Singleton3 {
private static volatile Singleton3 singleton3;
//构造器私有化
private Singleton3(){
}
public static Singleton3 getInstance(){
if (singleton3==null){
synchronized (Singleton3.class){
if (singleton3==null)
return new Singleton3();
}
}
return singleton3;
}
}
可能有人会说,为什么要双重锁啊,把外面的if (singleton3==null)这个判断去掉不也能保证线程的安全吗,其实说的也对,但是考虑到我们的运行效率,每个人都在等这把锁释放是不是有点浪费时间了。
还有个关键字volatile,这是用来保持内存可见性和防止指令重排序。内存可见性就是你一旦对这个值有修改,其他进程都能第一时间看到你修改后的值,防止指令重排序的话看个例子
java创建对象的过程:
- 调用构造函数,在堆中开辟一块空间,然后动态赋初值,比如int的话为0,引用型就为null
- 执行init方法为属性赋初始值
- 将堆空间的地址赋值给引用变量
这是我们理想的jvm运行顺序,但是有时候为了执行效率,可能进行指令重排,比如变成了1-3-2,这样当我们运行了1-3时,另一个进程进来了,他一看,唉?不为null,就直接得到了对象,这时候获得的对象是并没有赋值真实的初值的,用的话肯定会出现问题,那么volatile就能防止这种指令重排,使得我们拿到的都是完整的对象。
枚举类实现单例
public enum Singleton5 {
SINGLETON_5
}
看了很多博客,基本都推荐使用枚举,因为枚举实现的单例安全。其他方式实现的单例模式有可能会被破坏,比如利用反射和反序列化,反射的话有个方法 setAccessible(true)能访问私有的变量和方法,这样的话构造器的private就显得过于渺小了。枚举的话就能避免这些问题,而且还简单明了,我想这就是为什么越来越多人推荐吧。