1.什么是单例模式
2.单例模式的分类及其注意事项
1.什么是单例模式
简单说单例模式就是一个类始终只有唯一的一个对象,无法创建多个对象
为什么要有单例模式,也就是不能创建多个对象,是为了解决资源浪费,数据混乱,状态不一致等问题,数据混乱比如不同的对象中,有一个对象对数据进行了修改,其他对象不一定能同步,可能会继续使用旧值,状态不一致常见登录状态,登录状态无法保持全局一致
2.单例模式的分类
单例模式分为饿汉模式和懒汉模式---------实现单例模式的关键就在于构造方法私有化
(1)饿汉模式
java
public class Singleton {
//final防止二次赋值
//static修饰变为类属性,类加载就会被创建
private final static Singleton instance = new Singleton();//类加载就创建实例
public static Singleton getInstance(){
return instance;
}
private Singleton(){
}
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
}
}
饿汉模式就是非常饥渴,上来不管三七二十一先把对象创建了再说,哪怕你暂时用不到
那么就会出现问题,
1.1 浪费,而且如果初始化的对象数据庞大,那么将降低效率
1.2 无法延迟加载,比如你的项目原本要依赖某些配置文件,但还未配置好就加载项目了,会造成bug
但是饿汉模式天然线程安全,只涉及到读的操作
(2)懒汉模式
需要创建对象时才会实例化,涉及到条件判定+赋值的操作,因此懒汉模式会造成线程不安全问题
懒汉优点:支持懒加载,节省内存,适合大对象初始化
懒汉模式的线程不安全就在于首次创建对象时会产生线程不安全
需要注意三点
1.加锁
2.避免无效加锁
3.指令重排序问题
java
public class SingletonLazy {
//添加volatile避免指令重排序
private volatile static SingletonLazy instance = null;//类加载时并不会创建对象
/*//懒汉模式的关键在于,把实例化的创建时机推迟,推迟到第一次使用才创建
public static SingletonLazy getInstance(){
//进来先判断是否创建过,没创建过就创建,已经创建过就返回之前的
if(instance == null){
//但这里if判断与进来后赋值存在线程不安全的问题,这两者不是原子的,虽然说两个线程错开来创建了对象,
// 最后真正的对象是其中一个线程的最后赋值指令,另外一个线程创建的对象没有被引用会被自动回收,
// 但存在的问题在于如果对象构造加载的数据非常大呢?就相当于加载了多余的一份数据
instance = new SingletonLazy();
}
return instance;
}*/
/*public static Object object = new Object();
public static SingletonLazy getInstance(){
//这样一进入方法就加锁,加锁太多次了,效率低
synchronized (object){
if(instance == null){
instance = new SingletonLazy();
}
}
return instance;
}*/
public static Object object = new Object();
public static SingletonLazy getInstance(){
//这里判断是否要加锁
if(instance == null){
synchronized (object){
//这里判定是否要实例化
if(instance == null){
instance = new SingletonLazy();
}
}
}
return instance;
}
//单例模式的主要点在于不能让构造方法被调用,所以构造方法得设置为私有的
private SingletonLazy(){
}
public static void main(String[] args) {
SingletonLazy s1 = SingletonLazy.getInstance();
SingletonLazy s2 = SingletonLazy.getInstance();
System.out.println(s1 == s2);
}
}
1.加锁:由于判断和赋值不是原子的,加上CPU的随机调度,必然会产生线程不安全,所以要使用synchronized对这两个操作进行加锁,把他们变为原子的

2.避免无效加锁,就像上述代码,虽然已经进行了加锁,但每次需要获取到唯一对象时,只要一进入getinstance方法就会无脑加锁,要加锁又要解锁,开销大,降低效率,所以我们要再加上一个条件,进入这个方法时就要判断有没有实例化过对象,如果实例化过了就跳过这个加锁的过程,直接返回对象即可


所以我们能不加锁就不要加锁
3.指令重排序问题
指令重排序和内存可见性一样,都属于编译器优化的一种手段:调整指令执行的顺序,从而提高代码效率
就拿代码中的赋值对象语句: instance = new SingletonLazy()来说
它还能细分为三小步
(1)分配内存空间
(2)针对空间进行初始化
(3)内存空间首地址,赋值到引用变量中
正常来讲顺序为123 但由于指令重排序,可能会变为132,那么最后拿到的却是被初始化的对象,也就是一个null,相当于方便面先封口,再去装调料包,那么最后得到的是一包没有调料包的方便面
所以我们避免这个情况,使用volatile来对instance进行修饰,告诉编译器,对instance进行操作时,不需要对其进行优化