单例模式
优点:
单例模式在内存中只有一个实例,减少了内存的开支,
缺点:
没有单一缺口,扩展困难
在Spring中,每个Bean默认就是单例,这样的优点就是Spring容器可以管理这些Bean的生命周期,什么时候创建,什么时候销毁
简单点说,就是一个应用程序中,某个类的实例对象只有一个,你没有办法去new,因为构造器是被private修饰的,一般通过getInstance()的方法来获取它们的实例。
getInstance()的返回值是一个对象的引用,并不是一个新的实例,所以不要错误的理解成多个对象。单例模式实现起来也很容易,直接看demo吧
为了确保单例实例的唯一性,所有的单例构造器都要被声明为私有的(private),再通过声明静态(static)方法实现全局访问获取该单例实例
懒汉写法(线程不安全)
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
懒汉式写法(线程安全)
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
饿汉式写法
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
静态内部类
public class Singleton {
private Singleton (){}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
1)INSTANCE 实例只有当getInstance被调用时才会被实例化,从而达到实例延迟加载的目的;
2)内部类SingletonObj开始被调用,就会实例 INSTANCE 对象,从而达到线程安全的目的,该内部类和上述的饿汉式类似
双重校验锁
public class Singleton {
private volatile static Singleton singleton; //volatile防止指令重排序,内存可见(缓存中的变化及时刷到主存,并且其他的内存失效,必须从主存获取)
private Singleton (){} //构造器必须私有 不然直接new就可以创建
public static Singleton getSingleton() {
//第一次判断,假设会有好多线程,如果Singleton 没有被实例化,那么就会到下一步获取锁,只有一个能获取到,
//如果已经实例化,那么直接返回了,减少除了初始化时之外的所有锁获取等待过程
if (singleton == null) {
//第二次判断是因为假设有两个线程A、B,两个同时通过了第一个if,然后A获取了锁,进入然后判断doubleLock是null,他就实例化了doubleLock,然后他出了锁,
//这时候线程B经过等待A释放的锁,B获取锁了,如果没有第二个判断,那么他还是会去new DoubleLock(),再创建一个实例,所以为了防止这种情况,需要第二次判断
synchronized (Singleton.class) {
if (singleton == null) {
//下面这句代码其实分为三步:
//1.开辟内存分配给这个对象
//2.初始化对象
//3.将内存地址赋给虚拟机栈内存中的Singleton 变量
//注意上面这三步,第2步和第3步的顺序是随机的,这是计算机指令重排序的问题
//假设有两个线程,其中一个线程执行下面这行代码,如果第三步先执行了,就会把没有初始化的内存赋值给doubleLock
//然后恰好这时候有另一个线程执行了第一个判断if(singleton == null),然后就会发现singleton 指向了一个内存地址
//这另一个线程就直接返回了这个没有初始化的内存,所以要防止第2步和第3步重排序
singleton = new Singleton();
}
}
}
return singleton;
}
}
双重锁的方式使用了锁和双重判断的方法来实现目标单例的线程安全