文档章节

你真的会单例模式吗?

汉堡OSC
 汉堡OSC
发布于 2016/10/08 20:00
字数 1161
阅读 3
收藏 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
博文 26
码字总数 16921
作品 0
宝山
后端工程师
私信 提问
设计模式已经陨落了?

你现在是坐在一个程序员旁边吗?如果是的话,那么在你读下面的段落之前,有一个简单的实验。让他们到一边去,问问他们两个问题并记录下答案。首先问他们“什么是设计模式?”然后再问“说出你...

oschina
2014/03/11
9.1K
69
【设计模式笔记】(十六)- 代理模式

一、简述 代理模式(Proxy Pattern),为其他对象提供一个代理,并由代理对象控制原有对象的引用;也称为委托模式。 其实代理模式无论是在日常开发还是设计模式中,基本随处可见,中介者模式中...

MrTrying
06/24
0
0
Ubuntu中vi卸载与安装/使用模式

Ubuntu中安装的vi是vim-common版本,与centos系统中vi使用方式不同,编辑使用不惯, 遂卸载重装,卸载命令:sudo apt-get remove vim-common 卸载完毕后重新安装;输入命令:sudo apt-get in...

唐十三郎
11/27
0
0
学了那么多年设计模式依然不会用!那可真蠢!

什么是设计模式? 设计模式(Design Pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。 设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决...

GitChat技术杂谈
10/26
0
0
PHP设计模式(一):简介及创建型模式

我们分三篇文章来总结一下设计模式在PHP中的应用,这是第一篇创建型模式。 一、设计模式简介 首先我们来认识一下什么是设计模式: 设计模式是一套被反复使用、容易被他人理解的、可靠的代码设...

juhenj
2014/05/15
228
2

没有更多内容

加载失败,请刷新页面

加载更多

Spring Cloud Alibaba Sentinel 整合 Feign 的设计实现

作者 | Spring Cloud Alibaba 高级开发工程师洛夜 来自公众号阿里巴巴中间件投稿 前段时间 Hystrix 宣布不再维护之后(Hystrix 停止开发。。。Spring Cloud 何去何从?),Feign 作为一个跟 ...

Java技术栈
15分钟前
5
0
虚拟机加密

在超融合的基础设施和虚拟化成为常态的世界里,对加密的要求越来越高,越来越迫切,IT部门需考虑的重大安全问题和方法也浮现了出来。 物理数据中心时代,采取双保险式数据安全方法是相对简单...

linuxCool
18分钟前
1
0
MySQL 主从同步

MySQL主从介绍 MySQL主从又叫做Replication、AB复制。简单讲就是A和B两台机器做主从后,在A上写数据,另外一台B也会跟着写数据,两者数据实时同步的 MySQL主从是基于binlog的,主上须开启bin...

野雪球
30分钟前
1
0
OSChina 周一乱弹 —— 温柔的人应该这样

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @clouddyy :#每日一歌# 《フィクション-sumika》 《フィクション-sumika》 手机党少年们想听歌,请使劲儿戳(这里) 假期时间干嘛去, @for...

小小编辑
今天
270
7
[LintCode] Serialize and Deserialize Binary Tree(二叉树的序列化和反序列化)

描述 设计一个算法,并编写代码来序列化和反序列化二叉树。将树写入一个文件被称为“序列化”,读取文件后重建同样的二叉树被称为“反序列化”。 如何反序列化或序列化二叉树是没有限制的,你...

honeymose
今天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部