vlambda博客
学习文章列表

【设计模式】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 }
结果为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 140624128114062412811406241281898323977139505508139505508103512977

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(); } } }
说明: 上述写法仍是懒加载的方式,是谁保证了多线程下只有一个实例呢?就是JVM自身,在获取实例的时候JVM才加载内部类,且之只加载一次,保证一个实例的这一点和饿汉式是一个意思。

有了这么多单模式的实现,开发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进程)下是否是对象实例是否是单例、反序列化后被恶意创建实例等问题。

--- END ---

往期精彩回顾