细说Java单例设计模式

文章目录


啥叫设计模式?
设计模式就好比我们下象棋中的棋谱,红方当头炮,黑方马来跳。针对红发的一些走法,黑方下的时候有一些固定套路,按照套路来走局势就会吃亏。
就好比你打游戏玩某一个英雄,按照一定的打法打就不会打的太烂。
细说Java单例设计模式
而单例设计模是设计模式中非常常见的一种设计模式,单例模式能保证某个类在程序中只存在唯一一份实例,而不会创建多个实例。就比如我们在JDBC编程中,数据库的一些URL用户名就是唯一的实例。
单例设计模式非为两种,一种饿汉式懒汉式

饿汉模式

饿汉模式其实叫做立即加载,立即加载有着 着急、急迫的意思,所以又叫饿汉模式。饿汉模式是指使用类的时候已经将对象创建完毕了。

饿汉模式代码:

把构造方法设置成私有的,防止调用者在类外修改类的实例,把实例设置成也设置为私有同时设为类属性。
我们这里用静态内部类实现

public class TestDemo {
    //懒汉式
    static class Singleton{
        private static Singleton instance = new Singleton();
        //把构造方法设置私有,防止类外再创建实例
        private Singleton() {

        }
        //此处只涉及到读取操作,不存在线程安全问题
        public Singleton getInstance() {
            return instance;
        }
    }
}

懒汉模式

延时加载又叫懒汉模式,它是在调用 get() 方法实例才被创建。

来看一下代码:
这个代码在当单线程下肯定是没有问题的,但是在多线程下是肯定是会发生线程安全题的。

public class TestDemo2 {
    //懒汉式
    static class Singleton {
        private static Singleton instance = null;
        private Singleton() {

        }
        //这里涉及到多线程,多个线程同时涉及到修改的时候,会出现现线程不安全的问题
        public static Singleton getInstance() {
            if (instance == null) {
                //如果实例还没有创建,涉及到多个线程时,会涉及到修改,会发生线程安全问题
                instance = new Singleton();
            }
            //如果实例已经创建就不存在安全问题
            return instance;
        }
    }
}

就比如 if 判断这里的操作,假设我们有两个线程同时执行 if 操作。现是线程1执行了 if 操作为 null,接着线程2也执行了 if 操作 也为 null。

两个线程都进去了这个 if ,先是 线程1 执行了 new 操作,接着就是 线程 2执行了 new 操作,把之前的实例给覆盖了。好像覆盖了并没有什么影响。

举个列子:这个对象在极端情况下, new 这个对象需要消耗 10G内存,要好几分钟的时间。那能这么搞吗?

细说Java单例设计模式

细说Java单例设计模式

解决办法就是把 if 和 new 对象这两个对象变成原子的,也就是使用 synchronized 加锁。保证了原子性。

细说Java单例设计模式
getInstance() 方法是在首次调用的时候才会涉及到安全问题,所以我们在这加了一把锁。
那如果后续调用也不是会涉及到加锁操作吗?
后续本来没有线程安全问题了,已经不需要加锁,再尝试加锁,那不是多此一举吗?
加锁本来就是一个开销比较大的操作,此处不应该加锁的地方加锁了,可能会让这个代码的速度降低很多倍。

改进思路:首次调用的时候加锁,后续调用就不加锁了。

在最外层再加上一个 if 条件,区分是首批调用还是后续调用。
第一层 if :区分是首批线程还是后续调用的线程,决定是不是要加锁
因为 synchronized 加锁那里会发生阻塞等待,而且还不指定等多久,通过最外层的 if 之间限制了
里面这一层 if 是看第一批首先抢到锁的幸运儿才能访问的

当首批线程通过第一层if,进入到锁阶段,并创建好对象之后,这个时候,相当于已经把内存中 insertance的值修改成非 null了

后续批次的线程,通过第一层 if 的时候,也需要判定 instance 的值,但是这个判定不一定是从内存读取数据,也可能是从寄存器读数据了

细说Java单例设计模式
你以为这样就完了吗?其实并没有。
由于编译器的优化,在第一层 if 的时候,线程可能会之间去访问CPU寄存器里的值,这时候CPU寄存器里存的可能还是 null ,那么第一次 if 可能就失效了。
这就属于内存可见性的问题,
解决办法很简单 之间在volatile修饰 instance保证内存可见性。

细说Java单例设计模式
线程安全的懒汉式代码:

public class TestDemo3 {
    //线程安全的懒汉式
    static class Singleton {
        //加 volatile 保证内存可见性,防止编译器的优化。导致第一层 if 失效
        private volatile static Singleton instance = null;
        private Singleton() {

        }
        public static Singleton getInstance() {
            //这一层防止太多线程抢锁
            if(instance == null) {
                synchronized (Singleton.class) {
                    //只有一个幸运儿才能到这里
                    if (instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
}
上一篇:【DB笔试面试238】在Oracle中,如何将信息写入Oracle的告警日志中?


下一篇:单例模式(c#)