文档章节

你真的会单例模式吗?

汉堡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
宝山
后端工程师
java设计模式-- 单例模式

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

爱学习的逃课君
2014/11/27
0
0
JavaScript设计模式之观察者模式

前言 准备研究一下MVVM的一些东西,由于MVVM运用了观察者模式的思想,因此翻开了《JavaScript设计模式与开发实践》一书,将观察者模式学习了一遍,顺便有对一些常用的设计模式进行一些了解,...

Srtian
05/22
0
0
JavaScript 中常见设计模式整理

开发中,我们或多或少地接触了设计模式,但是很多时候不知道自己使用了哪种设计模式或者说该使用何种设计模式。本文意在梳理常见设计模式的特点,从而对它们有比较清晰的认知。 JavaScript 中...

牧云云
05/18
0
0
【设计模式笔记】(十六)- 代理模式

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

MrTrying
06/24
0
0
代理模式(Proxy Pattern):动态代理 - 最易懂的设计模式解析

前言 今天我来全面总结开发中最常用的设计模式 - 代理模式中的动态代理模式 其他设计模式介绍 1分钟全面了解“设计模式” 单例模式(Singleton) - 最易懂的设计模式解析 简单工厂模式(Sim...

Carson_Ho
04/09
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

MySQL 乱七八糟的可重复读隔离级别实现

MySQL 乱七八糟的可重复读隔离级别实现 摘要: 原文可阅读 http://www.iocoder.cn/Fight/MySQL-messy-implementation-of-repeatable-read-isolation-levels 「shimohq」欢迎转载,保留摘要,谢...

DemonsI
52分钟前
2
0
Spring源码阅读——2

在阅读源码之前,先了解下Spring的整体架构: 1、Spring的整体架构 1. Ioc(控制反转) Spring核心模块实现了Ioc的功能,它将类与类之间的依赖从代码中脱离出来,用配置的方式进行依赖关系描...

叶枫啦啦
今天
1
0
jQuery.post() 函数格式详解

jquery的Post方法$.post() $.post是jquery自带的一个方法,使用前需要引入jquery.js 语法:$.post(url,data,callback,type); url(必须):发送请求的地址,String类型 data(可选):发送给后台的...

森火
今天
0
0
referer是什么意思?

看看下面这个回答(打不开网页可以把网址复制到搜索栏): https://zhidao.baidu.com/question/577842068.html

杉下
今天
1
0
使用U盘安装CentOS-解决U盘找不到源

1. 使用UltraISO制作CentOS安装盘 如果需要安装带界面的系统,为保证安装顺利,可选择Everything版本的ISO制作安装盘。 2. 在BIOS中选择使用U盘安装 系统启动后,进入安装选择界面,其中有三...

Houor
今天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部