第6章 单例模式与多线程

本文摘自《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和静态代码块的特性相似,在使用枚举时,构造方法会被自动调用。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值