1. 单例模式
在实际开发中希望有的类在一个进程中不应该存在多个实例,此时就可以使用单例模式来限制某个类只能有唯一实例,例如DataSource这个类,一般来说一个程序中只有一个数据库,对应的mysql服务器只有一份,那么就没必要创建多个实例。
1.1. 饿汉式单例
饿汉式单例是在类加载的时候就创建实例,无论是否被使用,下面来看具体实现:
class Singleton { //随着类的加载而创建 private static Singleton instance = new Singleton(); private static Object lock = new Object(); //避免外界new对象 private Singleton(){}; public static Singleton getInstance() { return instance; } } public class ThreadDemo17 { public static void main(String[] args) { Singleton instance1 = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance1 == instance2); } }
这俩个实例地址是一样的,也就是同一个实例,为了防止其他类创建该类对象,构造方法可以设为私有的,但是也顶不住“恶意攻击”
1.2. 懒汉式单例
懒汉式单例是在第一次调用的时候才会创建实例,后续再调用的时候都不会创建实例
class SingletonLazy{ //此时先把实例的引用指向null,先不创建实例 private static SingletonLazy instance = null; private SingletonLazy(){ } public static SingletonLazy getInstance(){ //当实例没有创建过才会创建 if(instance == null){ instance = new SingletonLazy(); } return instance; } } public class ThreadDemo18 { public static void main(String[] args) { SingletonLazy s1 = SingletonLazy.getInstance(); SingletonLazy s2 = SingletonLazy.getInstance(); System.out.println(s1 == s2); } }
最终的结果也是和之前一样的
但是如果在多线程环境下是存在一个问题的,如果有线程t1和线程t2,t1执行到if判断时,此时刚好线程切换到t2再进行判断,然后t1创建了实例,由于t2判断的时候也是null,就会再次创建一个实例,这样就存在两个实例,虽然说第二次创建覆盖了第一次的值,第一个实例没有引用指向,很快就会被垃圾回收,但还是认为出现了线程安全问题
这种先判断再修改的代码是一种典型的线程不安全代码,判断和修改之间可能会切换线程
所以说需要加锁,但是这个锁怎么加呢?
正常情况下,我们都会把if和new的操作加锁锁起来,这样做确实可以解决线程安全问题,但是也带来了另一个问题:
第一次创建对象之后,后续再调用getInstance方法都只是单纯的读操作,不加锁也是线程安全的,此后每次调用方法都会加锁,但是也会因为加锁产生阻塞,影响性能,也就是第一次创建实例之后,后续的加锁操作都是没必要的,之前就已经提到过,synchronized使代码线程之后,什么时候恢复是未知的,可能其他线程就把这个值给改了
所以说需要再判断一次,如果实例已经不为空,直接返回就行
public static SingletonLazy getInstance() { //当实例没有创建过才会创建 if (instance == null) { synchronized (lock) { if (instance == null) { instance = new SingletonLazy(); } } } return instance; }
但是,上面的代码还是存在问题:
可能会因为指令重排序,引发线程安全问题
instance = new SingletonLazy();
这段代码中创建实例的过程可以粗略的分为3个指令:
- 分配内存空间
- 执行构造方法
- 将对象的引用指向分配的内存空间
指令重排列之后就可能是1,3,2的情况(不管怎么排列都是1先执行),就肯能会发生以下问题
解决办法就是使用volatile关键字:
private static volatile SingletonLazy instance = null;
加上之后就不会再出现指令重排序的问题了
懒汉模式是不会遇到上面发生的问题的