文档章节

单例模式、双重锁、无序写入

lmqian
 lmqian
发布于 2017/05/13 14:41
字数 959
阅读 40
收藏 0

之前在看Jfinal的源码的时候看到了这样一段代码

public static Prop use(String fileName, String encoding)
{
    Prop result = (Prop) map.get(fileName);
    if(result == null)  //#1
        synchronized (map)
        {
            result = (Prop) map.get(fileName);
            if(result == null)
            {
                result = new Prop(fileName, encoding); //#2
                map.put(fileName, result);
            }
        }
    return result;
}

其中,map是一个常量。

刚开始,我很疑惑,为什么要在同步代码块内外都要检查result是否为null。细想了一下才想明白:线程安全与性能之间的均衡。

1、假设线程A在同步代码块外检查到result为null,在进入同步代码块之前,线程B更新了map的内容,将对应键值对放进了map中。此时线程a进入同步代码块,因为会再获取并检查一次result是否为null,就减少了一次文件IO(result是从文件中获取的数据)。

2、假设同步代码块之前不去检查result是否为null,那么多个线程调用这个方法的时候,就都要排队,性能降低。

看上面的代码逻辑,似曾相识:跟单例模式的懒汉式类似,即在需要用到的时候再去加载对象,而不是在类加载的时候就生成对象。

一般使用单例模式就是三步:创建私有对象;私有化构造器;声明获取对象的公共方法。

下面是单例模式延迟加载(懒汉式)的代码示例:

public class Singleton
{
    private static Singleton singleton;
    
    private Singleton()
    {}
    
    public static Singleton getInstance()
    {
        if(singleton == null)
        {
            singleton = new Singleton();
        }
        return singleton;
    }
}

结合前面讲到的,很明显在多线程情况下,不能保证每个线程使用的是同一个对象。那么,就可以用到上面的那种方式了。这种方式被称为“双重检查锁”机制。代码就略掉了。

一般情况下,我们理解声明一个变量并进行初始化的顺序是:栈中创建变量;分配堆内存;创建对象;将栈中变量指向对象的地址

实际上,java平台内存模型(JMM)允许“无序写入”,也就是说处理器可能按这样的顺序执行:栈中创建变量;分配堆内存;将栈中变量指向对象的地址;创建对象

由此而产生的问题就是:在上面第一段代码中,假设线程A执行到#2处,线程B执行到#1处理,然后#2出现了无序写入,那么有可能线程B判断为false,result不为空,但是还并没有真正初始化数据,线程B拿到的可能是一个数据不完成的对象。

网上看到有说可以用volatile关键字来解决这个问题,我表示不太认同。

volatile变量对所有线程都是立即可见的,对volatile变量的写操作其他线程能够立即可见。但是它并没有加同步锁,在上面的例子中,即便使用volatile修饰,线程B在某些时刻还是有可能取到不完整的对象。

那么,究竟怎么来解决这个问题呢?

  1. 检查前直接使用同步锁,就是前面提到的性能降低的方法。
  2. 不使用延迟加载方式,在类加载的时候就初始化单例对象。
  3. 静态内部类
public class Singleton {  
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE; 
    }  
}

a. 满足了单例的要求。 b. 加载外部类的时候并不会加载内部类,只有第一次引用的时候才会加载内部类,使得单例对象是延迟创建的。

  1. Enum枚举
public enum Singleton{
    INSTANCE;
}

枚举的创建默认是线程安全的,缺点是没法继承和实现其他类。

© 著作权归作者所有

共有 人打赏支持
lmqian
粉丝 1
博文 4
码字总数 2681
作品 0
成都
程序员
私信 提问
结合 Android 看看单例模式怎么写

定义及使用场景 定义 单例模式,就是在整个系统中某一个类的实例只有一个,并且自行实例化向整个系统提供;简单来说,就是某个类被实例化的方式是唯一的;同时他它必须向系统自动提供这个实例...

IAM四十二
10/24
0
0
创建型模式.单例模式-懒汉、饿汉、枚举、原子

1 安全发布对象的四种方法 在多线程中,为了保证线程安全性,我们要正确地发布对象,保证发布地对象不要逸出。 在静态初始化函数中初始化一个对象引用 将对象的引用保存到volatile类型域或者...

阿杜杜不是阿木木
03/28
0
0
1、单例模式

定义: 单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例。即一个类只有一个对象实例。 特点: 1、单例类只...

晚天吹凉风
02/22
0
0
Java设计模式系列之单例设计模式

Java设计模式系列之单例设计模式 Hello,大家好,距离上次写博客是2018年1月26号,算了下,有8个月没写博客了。这里给大家道个歉,因为我换了工作,现就职在深圳一家公司,换了城市,加上工作...

我又不是架构师
10/08
0
0
Java单例模式

单例模式是一种普遍的设计模式,在任何情况下,都只有一个示例,并且也是单例类自己创建的示例,很多企业在笔试的时候会要求应聘者写单例模式的代码,这其中也把线程安全问题作为考察点,下面...

wangaowell
2017/04/25
2.4K
9

没有更多内容

加载失败,请刷新页面

加载更多

mybatis学习(1)

JDBC连接方式: 1.底层没有使用连接池,操作数据库需要频繁的创建和关闭连接,消耗资源。 2.写原生的JDBC代码在JAVA中,一旦需要修改SQL的话(比如表增加字段),JAVA需要整体重新编译,不利...

杨健-YJ
33分钟前
2
0
怎么组织文档

可以从以下几个方面考虑组织文档: ☐ 各种分支的界面截图和对应的类及文件 ☐ 框架或类图 ☐ 流程图 ☐ 时序图 ☐ 注意事项

-___-
45分钟前
3
0
分布式之数据库和缓存双写一致性方案解析

引言 为什么写这篇文章? 首先,缓存由于其高并发和高性能的特性,已经在项目中被广泛使用。在读取缓存方面,大家没啥疑问,都是按照下图的流程来进行业务操作。 但是在更新缓存方面,对于更...

别打我会飞
47分钟前
9
0
我的oracle11G,12c OCM之路

ocm认证感悟 ---------------------- 距离拿到ocm证书已经过了1年的时间,当初拿到证书的心情到现在还记得。其实在每个DBA心里都有一个成为强者的梦想,需要被认可,我也一样。我干过开发,做...

hnairdb
47分钟前
2
1
手动部署kubernetes集群(1.13.1最新版)

一、机器规划 使用五台机子部署k8s集群,规划如下: master节点3台(同时也是etcd节点) node节点2台 ip分配如下: ip:192.168.10.101,主机名:k8s-etcd01 ip:192.168.10.102,主机名:k8s...

人在艹木中
52分钟前
31
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部