java中的单例
发布日期:2022-02-06 00:26:58 浏览次数:21 分类:技术文章

本文共 3710 字,大约阅读时间需要 12 分钟。

什么是单例

单例对象的类必须保证只有一个实力存在----这是维基百科上对单例的定义,这也可以作为对意图实现单例模式的代码进行检验的标准。

单例分为两大类

1.懒汉式:指全局的单例实例在第一次被使用时构建。
2.饿汉式:指全局的单例实例在类装载时构建
平常我们使用较多的是懒汉式的单例

下面详细介绍一下两者的区别

1.懒汉式:

最简单的写法

public class Single1 {    private static Single1 instance;    public static Single1 getInstance() {        if (instance == null) {            instance = new Single1();        }        return instance;    }}//代码1.1public class Single1 {    private static Single1 instance;    //将构造方法变成私有的    private Single1() {}    public static Single1 getInstance() {    //判断是否已有实例对象        if (instance == null) {            instance = new Single1();        }        return instance;    }}

这种方法在大多数情况下是没有问题的,但是,当进行多线撑工作时,如果同时运行到if(instance==null),都判断为空的情况下,就会同时创建凉的线程的示例,就不属于单例了。

synchronized版本

synchronized版本

为了修改上面的问题,我们添加一个同步锁,修改代码如下:

//代码2

public class Single2 {    private static Single2 instance;    private Single2() {}    public static synchronized Single2 getInstance() {        if (instance == null) {            instance = new Single2();        }        return instance;    }}

我们在原来的基础上添加了一个synchronized关键字以后,getInstance方法就会锁上了,如果遇到了两个线程同时执行这个方法,就会有一个获得同步锁先执行方法,另一个则需要等大,第一个执行完之后,才会执行第二个。这样做就避免了可能出现因为多线程导致多个实例的情况。

但是这种方法也有一个问题:给gitInstance方法加锁,虽然会避免了可能会出现的多个实例问题,但是会强制除T1之外的所有线程等待,实际上会对程序的执行效率造成负面影响。

双重检查(Double-Check)版本

代码2相对于代码1的效率问题,其实是为了解决1%几率的问题,而使用了一个100%出现的防护盾。那有一个优化的思路,就是把100%出现的防护盾,也改为1%的几率出现,使之只出现在可能会导致多个实例出现的地方。

———我们使用下面的方法,来解决这个问题:

//代码3

public class Single3 {    private static Single3 instance;    private Single3() {}    public static Single3 getInstance() {        if (instance == null) {            synchronized (Single3.class) {                if (instance == null) {                    instance = new Single3();                }            }        }        return instance;    }}

这个方法相对来说比较复杂,其中出现了两次if(instancenull)的、判断,这个叫 双重检查

第一个if(instancenull)是为了解决代码2中的效率问题,只有instance为null的时候,才进入synchronized的代码块,大大的减少了机滤
第二个if(instance
null) ,是为了防止出现多个实例的情况。
为了是我们的代码看起来更加完美,我们又进行了一些处理,终极版本的代码如下:

终极版本:volatile

//代码4

public class Single4 {    private static volatile Single4 instance;    private Single4() {}    public static Single4 getInstance() {        if (instance == null) {            synchronized (Single4.class) {                if (instance == null) {                    instance = new Single4();                }            }        }        return instance;    }}

volatile关键字的作用是禁止指令重排(在不影响最终结果的情况下,可能会对一些语句的执行顺序进行调整。),把instance声明为volatile之后,对它的写操作就会有一个内存屏障,这样,在它的赋值完成之前,就不用会调用读操作。

注意: volatile阻止的不是singleton = new Singleton()这句话内部的指令重排,而是保证了在一个操作完成之前,不会调用读操作(if (instance == null))。

2.饿汉式:

由于类装载的过程是由类加载器来执行的,这个过程也是由JVM来保证同步的,所以这种方式先天就有一个优势——能够免疫许多由多线程引起的问题。

实现方法如下:

public class SingleB {    private static final SingleB INSTANCE = new SingleB();    private SingleB() {}    public static SingleB getInstance() {        return INSTANCE;    }}

对于饿汉式的单例,这个代码可以说是完美了,所以它出现的问题就是饿汉式单例本身的问题了——由于INSTANCE的初始化是在类加载时进行的,而类的加载是由ClassLoader来做的,所以开发者本来对于它初始化的时机就很难去准确把握。

==知识点:==什么时候是类装载时?

  1. new一个对象时
  2. 使用反射创建它的实例时
  3. 子类被加载时,如果父类还没被加载,就先加载父类
  4. jvm启动时执行的主类会首先被加载
  5. 一些其他的实现方式
    以上五种方法会触发类被加载。

Effective Java1 静态内部类

public class Singleton {    private static class SingletonHolder {        private static final Singleton INSTANCE = new Singleton();    }    private Singleton (){}    public static final Singleton getInstance() {        return SingletonHolder.INSTANCE;    }}

这种写法对于内部类SingletonHolder,它是一个饿汉式的单例实现,在SingletonHolder初始化的时候会由ClassLoader来保证同步,使INSTANCE是一个真·单例。同时,由于SingletonHolder是一个内部类,只在外部类的Singleton的==getInstance()中被使用,所以它被加载的时机也就是在getInstance()==方法第一次被调用的时候。

Effective Java 2 枚举

public enum SingleInstance {    INSTANCE;    public void fun1() {         // do something    }}

这种方法使代码更加简洁,而且还解决的了大部分的问题,想法非常优秀。但是在继承的场景中,就不是很适用了。

转载地址:https://blog.csdn.net/weixin_46175284/article/details/105100373 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:java中的继承和重写
下一篇:JAVA的多态

发表评论

最新留言

哈哈,博客排版真的漂亮呢~
[***.90.31.176]2024年04月12日 10时47分51秒