单例模式

我们都知道,JavaSE的三大设计模式是:工厂模式、代理模式、单例模式。
前两个模式博主已经写过了,有兴趣的可以看一下,今天我们来说说单例模式。

1.单例模式的三大特点:

  • 构造方法私有化
  • 类内部提供唯一静态私有对象
  • 类内部提供静态方法获取唯一对象

2.单例模式的分类:
1)饿汉式单例模式:

  • 特点:上来就new
  • 代码实现:
package www.first;

class HungrySingleton{
    //类内部提供唯一静态私有对象
    private static HungrySingleton hungrySingleton = new HungrySingleton();
    //构造方法私有化
    private HungrySingleton(){

    }
    //类内部提供静态方法获取唯一对象
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }
    public void hungry(){
        System.out.println("Hungry Singleton!");
    }
}

public class SingleTest {
    public static void main(String[] args) {
    //通过类内部提供的静态方法获取类的唯一对象,调用其方法
        HungrySingleton hungrySingleton = HungrySingleton.getInstance();
        hungrySingleton.hungry();
    }
}

结果如下:
在这里插入图片描述

2)懒汉式单例:

  • 特点:用时再new
  • 代码实现:
package www.first;

class LazySingleton{
    private static LazySingleton lazySingleton;
    private LazySingleton(){

    }
    public static LazySingleton getInstance(){
        if(lazySingleton==null){
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
    public void lazy(){
        System.out.println("Lazy Singleton!");
    }
}

public class LazySingleTest {
    public static void main(String[] args) {
        LazySingleton lazySingleton = LazySingleton.getInstance();
        lazySingleton.lazy();
    }
}

结果如下:
在这里插入图片描述
这样写有什么问题呢?
我们想一下,在多线程场景下,可能有多个线程同时进入if语句,这样会生成多个实例化对象,造成线程不安全。不难想到,我们可以使用加锁的方式来保证同一时刻只能有一个线程进入我们的实例化操作。

  • 优化版本:此时我们只改动静态获取实例的方法,保证同一时间只有一个线程进入实例化对象的操作,并且只产生一个实例化对象
public static LazySingleton getInstance(){
        //此时可能有多个线程进入if语句
        if(lazySingleton==null){
            //此时保证只有一个线程进入同步代码块
            synchronized (LazySingleton.class){
                //当线程1进入,实例化对象,退出同步代码块
                //线程2进入,此时已经有实例化对象,所以不再创建对象实例
                //此时保证了只产生一个实例化对象
                if(lazySingleton==null){
                    lazySingleton = new LazySingleton();
                }
            }
        }
        return lazySingleton;
    }

这样写看似没问题,但其实还是存在问题的,为什么呢?
lazySingleton = new LazySingleton();
new操作不具备原子性,这句话的执行包含以下三个步骤:

  • 1.在栈上为lazySingleton开辟一块空间;
  • 2.调用私有构造方法在堆上创建一个LazySingleton实例化对象;
  • 3.将栈上开辟的空间指向堆上的实例化对象。

我们都知道,JVM会对我们的代码进行指令重排序,假设JVM此时先开辟了一块栈内存空间,然后执行了第3步,即将栈上的空间指向了堆上的对象,此时还未实例化LazySingleton对象,但栈内存的值不为null;再进行第二步,则不会实例化对象,导致错误的产生。

  • 优化:可以使用volatile关键字来保证我们代码的有序性,防止指令重排序:
class LazySingleton{
    //防止指令重排序
    private static volatile LazySingleton lazySingleton;
    private LazySingleton(){

    }
    public static LazySingleton getInstance(){
        if(lazySingleton==null){
            synchronized (LazySingleton.class){
                if(lazySingleton==null){
                    lazySingleton = new LazySingleton();
                }
            }
        }
        return lazySingleton;
    }
    public void lazy(){
        System.out.println("Lazy Singleton!");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值