【设计模式】23种设计模式-单例模式
在程序设计中,很多时候我们只需要一个全局实例,如:Windows中的任务管理器、数据库连接池、线程池等等都是使用的是单例模式。
实现单例模式(Singleton)的方式有很多种。
实现单例需要考虑以下情况:
1、对象实例由谁来创建:不能交给他人,那么只能有对象自己去创建,所以构造方法需设置为私有private
2、多线程环境下能否保证只有一个实例
下面学习下常见的几种实现方式
饿汉式实现:
public class SingletonTest {
// 只要类被加载到JVM就创建一个实例
private static SingletonTest instance = new SingletonTest();
// 私有构造保证实例不能再被别人创建
private SingletonTest(){}
// 向外界提供一个获取唯一实例的接口
public static SingletonTest getInstance(){
return instance;
}
}
public static void main(String[] args) {
SingletonTest instance1 = SingletonTest.getInstance();
SingletonTest instance2 = SingletonTest.getInstance();
System.out.println(instance1 == instance2); // true
}
懒汉式实现
public class SingletonTest02 {
// 声明一个实例
private static SingletonTest02 instance;
// 私有构造保证实例不能再被别人创建
private SingletonTest02(){}
// 向外界提供一个获取唯一实例的接口
public static SingletonTest02 getInstance(){
if (instance == null){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
// 调用时再去创建实例
instance = new SingletonTest02();
}
return instance;
}
public static void main(String[] args) {
for (int i=0; i<100; i++){
new Thread(() -> {
System.out.println(SingletonTest02.getInstance().hashCode());
}).start();
}
}
}
测试:启动100个线程同时去获取实例,打印其hashcode。结果如下
1406241281
1406241281
1406241281
1406241281
898323977
139505508
139505508
103512977
hashcode不一样说明多线程环境下该写法产生了多个实例,与主题违背
改进的懒汉式
改进方案:同步锁+双重判断
// 向外界提供一个获取唯一实例的接口
public static SingletonTest03 getInstance(){
// 第一次判断
if (instance == null){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
// 调用时再去创建实例
synchronized(SingletonTest03.class){
// 二次判断
if (instance == null){
instance = new SingletonTest03();
}
return instance;
}
}
return instance;
}
说明:
第一个为空判断有人觉得可以去掉,去掉后从线程安全方面来说是没有线程安全问题的,但是从执行效率上并不是最好的,去掉的话有多少个线程就要持有多少个锁,效率低下。
public class SingletonTest04 {
// 私有构造保证实例不能再被别人创建
private SingletonTest04(){}
// 私有内部类
private static class Inner{
// 内部类中声明一个实例
private static final SingletonTest04 instance = new SingletonTest04();
}
// 向外界提供一个获取唯一实例的接口
public static SingletonTest04 getInstance(){
return Inner.instance;
}
public static void main(String[] args) {
for (int i=0; i<100; i++){
new Thread(() -> {
System.out.println(SingletonTest04.getInstance().hashCode());
}).start();
}
}
}
有了这么多单模式的实现,开发Java语言的作者之一看不下去了,直接一招解决问题
枚举实现
public enum SingletonTest05 {
instance;
public static void main(String[] args) {
for (int i=0; i<100; i++){
new Thread(() -> {
System.out.println(SingletonTest05.instance.hashCode());
}).start();
}
}
}
用枚举实现单例的优势:
1、保证线程安全
2、防止反序列化创建对象
以上是常见的实现单例实例的实现方法,各有优缺点,然而在实际设计单例过程中,考虑的问题不仅仅只有线程安全方面,如常见的分布式集群(多JVM进程)下是否是对象实例是否是单例、反序列化后被恶意创建实例等问题。
往期精彩回顾