本文摘自《Java多线程编程核心艺术》作者高洪岩
在标准的23个设计模式中,单例设计模式在应用中是比较常见的。但在使用多线程技术的单例模式时,容易出现一些意想不到的情况。
6.1 立即加载/“饿汉模式”
立即加载就是在使用类的时候已经将对象创建完毕,常见的实现办法就是直接new实例化。
package test;
public class MyObject {
//立即加载==饿汉模式
private static MyObject myObject = new MyObject();
private MyObject() {
}
public static MyObject getInstance() {
//此代码版本为立即加载
//此版本代码的缺点是不能有其他实例变量
//因为getInstance()方法没有同步
//所以有可能出现非线程安全问题
return myObject;
}
}
package extthread;
import test.MyObject;
public class MyThread extends Thread {
@Override
public void run(){
System.out.println(MyObject.getInstance().hashCode());
}
}
package test.run;
import extthread.MyThread;
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.start();
t2.start();
t3.start();
}
//输出结果如下,其中hashCode为同一个值,说明对象是同一个,也就实现了立即加载型单例设计模式
29115481
29115481
29115481
6.2 延迟加载/“懒汉模式”
延迟加载就是在调用get()方法时实例才被创建,常见的办法就是在get()方法中new实例化。
//错误示例,因为多个线程可以同时进入getInstance()方法,多个线程可能创建多个实例
package test;
public class MyObject {
private static MyObject myObejct;
private MyObject() {
}
public static MyObject getInstance() {
//延迟加载
if (myObject != null) { //多线程中会出问题
} else {
myObject = new MyObject();
}
return myObject;
}
}
解决方法:使用DCL双检查锁机制。因为只使用同步方法和同步语句块时效率很低,且如果只同步new语句块,效率提升,但由于只有一处判断,还是会出现非线程安全问题。
package test;
public class MyObject {
private static MyObject myObject;
private MyObject(){
}
public static MyObject getInstance() {
try {
if (myObject !== null) {
} else {
//模拟在创建对象之前做一些准备性的工作
Thread.sleep(3000);
syschronized (MyObject.class) {
if (myObject == null) {
myObject = new MyObject();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject();
}
}
6.3 使用静态内置类实现单例模式
除了DCL可以解决多线程单例模式的非线程安全问题外,还可以使用静态内置类实现。
package test;
public class MyObeject {
//内部类方式
private static class MyObjectHandler {
private static MyObject myObject = new MyObject();
}
private MyObject() {
}
public static MyObject getInstance() {
return MyObjectHandler.myObject;
}
}
6.4 序列化与反序列化的单例模式实现
使用静态内置类可以达到线程安全问题,但如果遇到序列化对象时,使用默认的方式运行得到的结果还是多例的。解决办法就是在反序列化时使用readResolve()方法。
package test;
import java.io.ObjectStreamException;
import java.io.Serializable;
public class MyObeject implements Serializable {
private static final long serialVersionUID = 888L;
//内部类方式
private static class MyObjectHandler {
private static MyObject myObject = new MyObject();
}
private MyObject() {
}
public static MyObject getInstance() {
return MyObjectHandler.myObject;
}
protect Object readResolve() throws ObjectStreamException {
return MyObjectHandler.myObject;
}
}
6.5 使用static代码块实现单例模式
静态代码块中的代码在使用类的时候就已经执行了,所以可以使用静态代码块的这个特性来实现单例设计模式。
package test;
public class MyObject {
private static MyObject instance = null;
private MyObject() {
}
static {
instance = new MyObject();
}
public static MyObject getInstance() {
return instance;
}
}
6.6 使用enum枚举数据类型实现单例模式
枚举enum和静态代码块的特性相似,在使用枚举时,构造方法会被自动调用。