文档章节

你真的会单例模式吗?

汉堡OSC
 汉堡OSC
发布于 2016/10/08 20:00
字数 1161
阅读 2
收藏 0

单例模式怎么写?大家都知道单例模式分为:懒汉式和饿汉式。

一、首先我们来一个非线程安全的例子:

public class SingletonTest
{
    public static void main(String ...args) throws Exception{
        MyThread ta=new MyThread();
        MyThread tb=new MyThread();
        ta.start();
        tb.start();
        ta.join();
        tb.join();
    }
}


class Singleton
{
    private static Singleton instance;
    private Singleton(){}
   
    public static Singleton getInstance(){
        if(instance ==null){
            instance =new Singleton();
            System.out.println("new Singleton");
        }
        return instance;
    }
}


class MyThread extends Thread
{
    public MyThread(){
    }

    public void run(){
        for(int i=0;i<3;i++){
            Singleton.getInstance();
        }
    }
}

一个很简单的例子,打印结果如图,理论上new Singleton这句话应该只被打印1次的,但运行结果是有时一次、有时是两次:

image

出现这个现象的原因很简单,在new Singleton之前,多个线程进入if(instance ==null)就会导致这个现象。

二、懒汉式,线程安全的写法

public static synchronized Singleton getInstance() {

    if (instance == null) {

        instance = new Singleton();

    }

    return instance;

}

这种是在方法上加synchronized,即默认使用当前对象作为锁,而且同一时间内只有一个线程能进入该方法内。这种方式实现了真正的线程安全,但是并发的性能不高,因为在任何时候只能有一个线程调用 getInstance() 方法。

三、双重检验锁 DCL(double checked locking)

public static Singleton getSingleton() {

    if (instance == null) {                         //Single Checked

        synchronized (Singleton.class) {

            if (instance == null) {                 //Double Checked

                instance = new Singleton();

            }

        }

    }

    return instance ;

}

为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 if    (这个时候多个线程都在等锁),如果在同步块内不进行二次检验的话就会生成多个实例了。那么这种方式是线程安全的吗?答案是肯定的,但还有网上还有这个说法“

这段代码看起来很完美,很可惜,它是有问题。主要在于instance = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。

  1. 给 instance 分配内存

  2. 调用 Singleton 的构造函数来初始化成员变量

  3. 将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)

但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。

我们只需要将 instance 变量声明成 volatile 就可以了。即private volatile static Singleton instance;

正确与否还未验证(我用这种方法写多次运行没报错,看的张振华著的《Java并发编程从入门到精通》里面说这种方法也是没问题的)。

四、饿汉式

又称静态工厂实现方法,这种方法非常简单,因为单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。

public class Singleton{

    //类加载时就初始化

    private static final Singleton instance = new Singleton();

    private Singleton(){}

    public static Singleton getInstance(){

        return instance;

    }

}

 

这种写法如果完美的话,就没必要在啰嗦那么多双检锁的问题了。缺点是它不是一种懒加载模式(lazy initialization),单例会在加载类后一开始就被初始化,即使客户端没有调用 getInstance()方法。饿汉式的创建方式在一些场景中将无法使用:譬如 Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance() 之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。

五、静态内部类

静态内部类只有在return SingletonHolder.INSTANCE时才被调用,且只调用一次,Think in java里面推荐这种写法

public class Singleton { 

    private static class SingletonHolder { 

        private static final Singleton INSTANCE = new Singleton(); 

    } 

    private Singleton (){} 

    public static final Singleton getInstance() { 

        return SingletonHolder.INSTANCE;

    } 

}

 

这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。

六、枚举

一种非常简单的写法,Effective Java中推荐的。

public enum Singleton {

INSTANCE;

public void whateverMethod() { }

}

参考一些文章做的笔记,如有错误请及时指出,谢谢。

© 著作权归作者所有

共有 人打赏支持
汉堡OSC

汉堡OSC

粉丝 4
博文 24
码字总数 16013
作品 0
宝山
后端工程师
php各种设计模式简单实践思考

前言 我一直觉得什么框架,版本,甚至语言对于一个coder来说真的不算什么,掌握一个特别高大上的一个框架或者是一个新的,少众的语言真的不算什么,因为你可以,我要花时间也可以,大家都是这...

michaelgbw
2016/06/13
0
0
JavaScript设计模式入坑

JavaScript设计模式入坑 介绍 设计模式编写易于维护的代码。 设计模式的开创者是一位土木工程师。Σ( ° △ °|||)︴,写代码就是盖房子。 模式 模式一种可以复用的解决方案。解决软件设计中...

小小小8021
10/18
0
0
java设计模式-- 单例模式

在很久之前,也就是在大二暑假的时候,那时候看马士兵的视频教程中有提到很多的设计模式。 java的设计模式大致可以分为3大类,23种设计模式。 其中,创建型模式有5种:单例模式、建造者模式、...

爱学习的逃课君
2014/11/27
0
0
编程中的那些套路——关于策略模式

该文章属于《编程中的那些经典套路——设计模式汇总》系列,并且以下内容基于语言PHP 今天讲讲策略模式,策略模式 和工厂模式十分相像(或者说在代码逻辑层面,他们是一样的)。 但策略模式与...

gzchen
08/27
0
0
编程中的那些经典套路——设计模式汇总

在正式阅读前,我先谈谈我们该用什么姿势和心态学习设计模式: 如果你还没有过多的编程经验(泛指半年以下),我建议你把它当做小说来看,能看懂多少是多少,因为半年以下经验的程序员用到设...

gzchen
08/27
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Ubuntu18.04下载安装Google图解法

首先Ctrl + Alt + T打开终端,或者可以在在搜索框搜索终端。 (1)sudo wget http://www.linuxidc.com/files/repo/google-chrome.list -P /etc/apt/sources.list.d/ ,(将下载源加入到系统的...

AI_SKI
26分钟前
0
0
spring could采坑 eureka开启验证后无法连接注册中心

com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server at com.netflix.discovery.shared.transport.decorator.RetryableEurekaHttpClie......

君千殇520
27分钟前
2
0
支付宝小程序下单支付接口:40004 ACQ.INVALID_PARAMETER

下面是支付宝下单接口 https://docs.open.alipay.com/api_1/alipay.trade.create/ 如果按官方文档的说明,并没有解释清楚buyer_id什么时候要传,只是说:特殊可选,啥叫特殊可选?!在调小程...

swingcoder
36分钟前
1
0
【Java】广州三本秋招经历

前言 只有光头才能变强 离上次发文章已经快两个月时间了,最近一直忙着秋招的事。今天是2018年10月22日,对于互联网行业来说,秋招就基本结束了。我这边的流程也走完了(不再笔试/面试了),所...

Java3y
38分钟前
3
0
在idea中启动多个SpringBoot项目

https://blog.csdn.net/forezp/article/details/76408139

灯下草虫鸣_
39分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部