细说Java单例设计模式

Posted 爱敲代码的三毛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了细说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内存,要好几分钟的时间。那能这么搞吗?

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


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

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

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

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

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


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


线程安全的懒汉式代码:

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;
        }
    }
}

以上是关于细说Java单例设计模式的主要内容,如果未能解决你的问题,请参考以下文章

设计模式之单例模式

java的单例模式怎么能保证始终是单例

常用代码片段

常用代码片段

Java中的单例模式

java 单例模式这个要怎么理解?