java设计模式的单例模式_Java设计模式之单例模式 - Singleton

用来创建独一无二的,是能有一个实例的对象的入场券。告诉你一个好消息,单例模式的类图可以说是所有模式的类图中最简单的,事实上,它的类图上只有一个类!但是,可不要兴奋过头,尽管从类设计的视角来说很简单,但是实现上还是会遇到相当多的波折。所以,系好安全带,出发了!

介绍

定义

单例模式(singleton pattern):确保一个类只有一个实例,并提供一个全局访问点。

常用情景

有些对象其实我们只需要一个,比如:windows的任务管理器,项目中的读取配置文件的对象,数据库连接池,spring中的bean默认也是单例,线程池(threadpool),缓存(cache),对话框,处理偏好设置和注册表(registry)的对象,日志对象,充当打印机,显卡等设备的驱动程序的对象。事实上,这类对象只能有一个实例,如果制造出多个实例,就会导致许多问题产生,例如:程序的行为异常,资源使用过量,或者是不一致的结果。

要点

优点

单例模式存在一个全局访问点,所以优化共享资源;

只生成一个实例,减少了开销,对于一些需要频繁创建和销毁的对象,单例模式可以提高系统性能。

缺点

由于单例模式中没有抽象层,因此扩展困难;

职责过重,在一定程度上违背了“但一职责原则”。

类图

在java中实现单例模式需要注意以下三点:

私有的构造器;

一个静态方法;

一个静态变量。

单例模式的实现

饿汉式

代码实现

/** 通过饿汉式创建单例模式

* 当前类只能创建一个对象

* 天然线程安全(类只要被加载,就会被加载到全局变量中。所以饿汉式就是及时加载,不能实现懒加载)

*/

public class singlehungry {

//提供一个静态的全局变量作为访问该实例的入口

private static singlehungry instance = new singlehungry();

/**

* 构造器私有,不让外部通过new创建实例

*/

private singlehungry() {}

/**

* 对外提供静态的方法,用来获取该类的实例

*/

public static singlehungry getinstance() {

return instance;

}

}

要点

优点:线程安全;

缺点:不能懒加载。

懒汉式

代码实现

/**

* 懒汉式,完成单例模式:

* 静态的全局变量,初始化放在了静态方法中,延迟产生了实例

* 延迟加载

* 线程不安全

*/

public class singlelazy {

//提供一个静态的全局变量作为访问该实例的入口,但不立即加载

private static singlelazy instance = null;

/**

* 构造器私有

*/

private singlelazy() {

system.out.println("构造器被调用了");

}

/**

* 提供方法获取该类的实例

* @return

*/

public static singlelazy getinstance() {

//先查看是否存在对象,不存在则创建

if(instance == null) {

instance = new singlelazy();

}

return instance;

}

}

以上代码存在的问题如图:

16427545e3f849d12562e68591f737b8.png

这段代码是线程不安全的,当有多个线程同时访问该代码时,会出现创建多个实例的情况。

为了方便理解,我以head first设计模式中的图来介绍:

d787dbdbb92bcbb23ac202c60cbe326e.png

以上模拟了多线程问题的运行步骤,只是对象的名称不一样而已,相信对你来说是没问题的。

解决思路1:

加锁。锁方法

c5583ad6519f678e59ca57bef4250529.png

问题:可以解决问题,但锁住整个方法的粒度太大了,效率较低。不推荐使用

解决思路2:

加锁。锁代码块,先判断后锁

9ae6bef70a02c3b5a7ba400c3aef3f24.png

问题:不能解决问题,以上方式锁的粒度变小了,但是并不能产生一个实例。原因:多个线程判断之后全局变量都是null,进入后都开始等锁。线程1出去,线程2进来继续实例化,所以得到的对象是多个。

解决思路3:

加锁。锁代码块,先锁后判断。

bfe6280c5be88e565b71978554df1f57.png

以上方式解决了问题,当有多个线程同时访问getinstance()。保证了多个线程是以流式,次序性的进入当前方法来获取该类的实例。那么效率一样很低。而且多个线程同时等待。

上面的解决方法似乎都不怎么好,那么有没有一种更好的方法来解决懒汉式的多线程安全问题呢?请继续探索... ...

双重检测(双重校验锁)

代码实现

public class singlelazy {

/*

* 双重检测(双重校验锁)

* 延迟加载

* 线程安全

*/

private static volatile singlelazy instance;

/*

* 构造器私有

*/

private singlelazy() {

system.out.println("构造器被调用了");

}

public static singlelazy getinstance() {

if(instance == null) {

synchronized (singlelazy.class) {

if(instance == null) {

instance = new singlelazy();

}

}

}

return instance;

}

}

要点

volatile关键字确保:当instance变量被初始化成singleton实例时,多个线程正确地处理instance变量。

值得注意的是:很不幸地,在jdk1.5之前的版本,许多jvm对于volatile关键字的实现会导致dcl(double check locking)失效。如果你不能使用jdk1.4之后的版本,就请不要利用此技巧实现单例模式。

双重检测很好的解决了懒汉式多线程安全问题,可以和之前的几种解决思路对比一下,思考一下优点在哪里!

解决思路:

我们知道为了解决这个线程安全问题,必须加锁,并且必须先锁后判断,思路3已经解决了问题,为了进一步优化代码执行效率,我们再来改进一下代码:

867d73409921bea6a1fc6d70c96a54ac.png

我们在锁的外面再加一层对全局变量的判断,这么做的效果是什么呢?当有多个线程来访问时,例如:

线程1,线程2同时进入该方法;

经过最外面的判断后,发现还没有创建实例,这时就会依次执行锁里的逻辑,并创建了实例;

假如这时线程3进入该方法,执行最外面的判断,发现已经创建了实例,这时候直接返回就可以了,并不需要等锁。

静态内部类(饿汉式的变种)

代码实现

/*

* 静态内部类创建单例(利用类加载机制,保证线程安全问题)

* 线程安全

* 懒加载

*/

public class singleinner {

private singleinner() {}

private static class singleinnerholder{

private static singleinner instance = new singleinner();

}

public static singleinner getinsstance() {

return singleinnerholder.instance;

}

}

要点

和饿汉式一样采用的是classloader机制,保证了线程安全问题,但不同的是,静态内部类同样满足懒加载(当调用getinsstance()方法时,实例才会被创建)。

枚举实现

代码实现

public enum singleenum {

//定义示例化的单例对象

instance;

/*

* 对象执行的功能

*/

public void getinstance() {

}

}

要点

枚举类的单例模式,不存在出现序列化,反射构建对象的漏洞(前面几种单例实现方式都存在此问题)。不能懒加载。默认情况枚举实例的创建都是线程安全,但是实例对象实现的方法,需要自己保证线程安全问题。

反射,序列化与单例的关系

反射

我们以饿汉式为例,如下图:

48d69aa77542cc6f118653ec4dadf04e.png

通过反射获取的s3和通过饿汉式获取的s1,s2,并不是同一个实例,那么有什么办法可以解决反射对单例的影响呢!请看下面:

解决方法:

我们通过改变饿汉式构造方法的方式,来解决这个问题:

6e51ecfc0f3800e5f088bfe7683df6aa.png

调用时抛出异常:

eda63324a63db0929c6aed4b1438d6c1.png

序列化

依旧以饿汉式为例,如下图:

fe1c8bd19c5bf32532174040ea0c49eb.png

通过序列化,反序列化获取的s3和通过饿汉式获取的s1,s2,并不是同一个实例,那么有什么办法可以解决序列化,反序列化对单例的影响呢!请看下面:

cb02e6ee18150feece512be6f105645a.png

通过在饿汉式中加入此方法,就可以解决这个问题:

bb791ffa4388e07ac8d14c43b8e3f5ea.png

创建的构建单例模式的方式比较

饿汉式 效率较高 不能懒加载 线程安全 调用率高

懒汉式 效率较低 懒加载 线程不安全

双重检测(双重校验锁)懒汉式的一个变种 效率较高 懒加载 线程安全

静态内部类 饿汉式的变种 效率较高 懒加载 线程安全

枚举 效率较高 线程安全 不能懒加载

用法总结

懒汉式效率最低;

占用资源较少 不需要懒加载 枚举优先 饿汉式;

占用资源较多 需要懒加载 静态内部类优先 懒汉式(优先使用dcl)。

结语

设计模式源于生活

希望与广大网友互动??

点此进行留言吧!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值