【JavaEE】单例模式(饿汉&懒汉)

目录

前言:

单线程下的单例模式

饿汉模式

懒汉模式

多线程下的单例模式

懒汉模式的修改

1.加锁

2.有条件的加锁

3.解决内存可见性和指令重排序


前言:

本片文章介绍设计模式中的一个模式——单例模式。

单例模式就是只允许创建出一个实例的类。比如之前在使用JDBC编程的时候的DataSource这个类,就可以使用单例模式。见这篇文章http://t.csdn.cn/uq1lR

其中单例模式有很多种实现方法,这里只用懒汉模式和饿汉模式来实现单例模式。


单线程下的单例模式

单线程中,没有线程安全问题,其代码会简单很多。

饿汉模式

饿汉模式体现出一个字——急。因为这个实例直接就是在类加载阶段就被创建出来。

class SingletonHungry{

    // 用static修饰,这样在类加载阶段就有了这个实例
    private static SingletonHungry instance = new SingletonHungry();

    // 用静态公开方法返回实例
    public static SingletonHungry getInstance() {
        return instance;
    }

    // 为了防止实例化多个对象,把构造方法用private修饰
    private SingletonHungry(){};

}

public class SingletonTest1 {

    public static void main(String[] args) {

        SingletonHungry instance1 = SingletonHungry.getInstance();
        SingletonHungry instance2 = SingletonHungry.getInstance();
        System.out.println(instance1 == instance2);

        //SingletonHungry instance3 = new SingletonHungry();

    }

}

 

 


懒汉模式

懒汉模式突出一个字——懒。这个实例是只有调用获取实例方法的时候才创建出来。

class SingletonLazy{
    private static SingletonLazy instance = null;

    // 只有在调用了该方法后才创造出了实例
    public static SingletonLazy getInstance() {
        // 如果已经创建了该实例,就直接返回之前的实例
        if (instance == null) {
            instance =  new SingletonLazy();
        }
        return instance;
    }

    // 为了防止实例化多个对象,把构造方法用private修饰
    private SingletonLazy(){}
}

public class SingletonTest2 {

    public static void main(String[] args) {

        SingletonLazy instance1 = SingletonLazy.getInstance();
        SingletonLazy instance2 = SingletonLazy.getInstance();
        System.out.println(instance1 == instance2);

        //SingletonLazy instance3 = new SingletonLazy();
    }

}

代码结果如上。 


多线程下的单例模式

在上述代码中,如果考虑多线程的话

饿汉模式没有线程安全问题,它直接就是在类加载是创建出了一个实例,使用该实例也没有其他修改的操作,直接返回即可。

懒汉模式是有线程安全问题。它又要判断比较实例是否为null,又要创建一个实例,最后才返回实例。其中对于实例是由修改的操作的。只要有修改操作,就可能会有线程安全问题。如下图:

懒汉模式的修改

1.加锁

对于这种又有读,又有写的操作,保持原子性——加锁即可。

    public SingletonThread getInstance() {
        // 这里对于load cmp new这几步加锁,保持了其原子性
        synchronized (SingletonThread.class) {
            if (instance == null) {
                instance = new SingletonThread();
            }
        }
        return instance;
    }

2.有条件的加锁

但是加锁是一种开销比较大的操作,上述加锁操作并不是每次都要加锁的。如果已经创建了实例,直接返回实例即可。

    public SingletonThread getInstance() {
        // 如果实例未被创建,用加锁的方法创建实例
        // 如果创建,直接返回
        if (instance == null) {
            // 这里对于load cmp new这几步加锁,保持了其原子性
            synchronized (SingletonThread.class) {
                if (instance == null) {
                    instance = new SingletonThread();
                }
            }
        }
        return instance;
    }

3.解决内存可见性和指令重排序

内存可见性:因为instance要读取并修改,所以对于内存可见性的问题也要预防。

指令重排序:

上面的new实例的指令又分为三个顺序步骤:

①申请内存空间

②调用构造方法,实例化一个对象

③把内存空间的地址赋值给这个对象

这几步可能可能会变成①③②。单线程下没有问题,但是多线程就会有问题了。

要想解决这两个问题,使用volatile关键字修饰instance即可。

    private volatile static SingletonThread instance = null;

完整的懒汉模式的线程安全代码如下:

// 修改懒汉模式,使其线程安全
class SingletonThread {
    // 使用volatile解决内存可见性和指令重排序问题
    private volatile static SingletonThread instance = null;

    public SingletonThread getInstance() {
        // 如果实例未被创建,用加锁的方法创建实例
        // 如果创建,直接返回
        if (instance == null) {
            // 这里对于load cmp new这几步加锁,保持了其原子性
            synchronized (SingletonThread.class) {
                if (instance == null) {
                    instance = new SingletonThread();
                }
            }
        }
        return instance;
    }

}

有什么问题评论区指出。希望可以帮到你。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值