解决方案:DCL

如果使用synchronized关键字锁定整个getInstance()或整个if语句块,则会存在效率问题。

最后,采用DCL(double-Check Locking)双校验锁定机制,这也是大多数多线程结合单例模式的解决方案。第一层主要是为了避免不必要的同步,第二层是在空情况下创建实例。

java dict设计 java dcl_java dict设计

测试结果,得到的是相同的hashcode。

立即加载:“饿汉模式”

立即加载意味着在使用类时创建了对象。常见的实现方法是直接新实例化。也就是说,在调用方法之前,创建实例。示例代码如下:

1 classMyObject {
2 private static MyObject myObject=newMyObject();
3 privateMyObject(){}
4 public staticMyObject getInstance(){
5 //如果还有其他代码,存在线程安全问题
6 returnmyObject;
7 }
8 }
9 class MyThread extendsThread{
10 @Override
11 public voidrun() {
12 System.out.println(MyObject.getInstance().hashCode());
13 }
14 }
15 public classRun {
16 public static voidmain(String[] args) {
17 MyThread t1=newMyThread();
18 MyThread t2=newMyThread();
19 MyThread t3=newMyThread();
20 t1.start();
21 t2.start();
22 t3.start();
23 }
24 }

运行结果如下:

java dict设计 java dcl_线程安全_02

可以发现,实现了单例模式,因为多个线程得到的实例的hashCode是一样的。

静态内置类

java dict设计 java dcl_java dict设计_03

采用静态内置类的方法,是线程安全的。

使用static代码块

静态代码块的代码在重用类时执行,因此可以应用静态代码块的这个特性来实现单例设计模式。

java dict设计 java dcl_java dict设计_04

使用enum枚举数据类型

当使用枚举类时,类似于静态代码块,自动调用构造函数。枚举由javac编译,并转换为诸如公共最终类T extends Enum之类的定义。也就是说,我们定义的枚举在第一次真正使用时由虚拟机加载和初始化,并且初始化过程是线程安全的。众所周知,解决单例并发问题的主要方法是初始化过程中的线程安全问题。

因此,由于枚举的上述特性,枚举实现了固有的线程安全的单例。同时,枚举可以解决反序列化会破坏单例的问题。

enum MyObject{
INSTANCE;
}
SimpleDataFormat

SimpleDataFormat使用带有线程安全问题的单例模式。SimpleDateFormat中的日期格式不同步。建议(建议)为每个线程创建单独的格式实例。如果多个线程同时访问一种格式,则它必须保持外部同步。

解决方案1:需要的时候创建新实例

java dict设计 java dcl_单例编程java_05

在需要SimpleDateFormat的地方创建新实例可以通过在任何时候将线程安全的对象从共享更改为本地私有来避免多线程,但是它也增加了创建对象的负担。一般来说,对性能的影响不是很明显。

解决方案2:同步SimpleDateFormat对象

java dict设计 java dcl_解决方案_06

当有更多的线程时,当一个线程调用此方法时,其他想要调用此方法的线程需要块。当多线程并发性较大时,会对性能产生一定的影响。

解决方案3:使用ThreadLocal

java dict设计 java dcl_线程安全_07

ThreadLocal还用于将共享变量转换为独占变量。ThreadLocal肯定比方法独占性减少了在并发环境中创建对象的开销。如果性能要求很高,通常建议使用这种方法。