JavaEE--单例模式

1.设计模式

设计模式是在软件开发中解决常见问题的最佳实践或方案。使用设计模式可以实现可重用代码,帮助我们创建更灵活、可维护和可扩展的代码。而我们要介绍的单例模式就是设计模式中比较经典的一种模式。

2.单例模式

单例模式就是单个实例,在整个进程中的某个类,有且只有一个对象,这样的对象就被称为“单例”,那么如何保证这个类只有一个实例呢?这就需要编译器来帮我们做一个强制的检查,通过一些编程上的技巧,使编译器可以自动发现代码中是否含有多个实例,并且在尝试创建多个实例时,直接编译报错,从根本上保证对象是唯一实例,这样的代码就称为单例模式。

单例模式的实现方式有很多,我们主要介绍“饿汉模式”与“懒汉模式”

 2.1 饿汉模式

唯一实例的创建时间非常早,在类加载的同时就创建实例(就像一个饿了很久的人,看到吃的就迫不及待地开始吃起来)

public class Singleton {
    private static Singleton instance=new Singleton();//static成员在类加载时初始化
    public static Singleton getInstance(){
        return instance;
    }
    private Singleton(){
     //类外的代码在尝试new的时候,需要调用构造方法,由于构造方法是私有的,无法调用,就会编译出错
        //通过用private修饰构造方法,有效地禁止了外部代码创建该类的实例
    }
}
class Hungry{
      Singleton singleton1 = Singleton.getInstance();
      Singleton singleton2 = Singleton.getInstance();
}

2.2 懒汉模式

不是在程序启动的时候创建实例,而是在第一次使用的时候才去创建实例

public class Singleton1 {
    private static Singleton1 instance = null;
    public static Singleton1 getInstance() {//什么时候调用,什么时候创建
        if(instance == null) {
            instance = new Singleton1();
        }
        return instance;
    }
    private Singleton1(){

    }
}
class Lazy{
    Singleton1 singleton1 = Singleton1.getInstance();
    Singleton1 singleton2 = Singleton1.getInstance();
}

3.是否线程安全

在单线程环境下,上述两种模式的代码都够正常执行,但是当处于多线程环境中,有多个线程调用getInstance方法时,这两种模式会不会出现线程安全问题呢?

3.1 饿汉模式(线程安全)

在饿汉模式中,实例创建的时间是在java进程启动时(比main线程被调用还早),后续在代码中创建线程一定比实例的创建更加晚,所以当后续线程在执行getInstance方法时,实例早就已经创建完毕,而getInstance方法只是去读取了这个创建好的实例的值,相当于多个线程去读取同一个变量,不存在线程安全问题

3.2 懒汉模式(线程不安全)及改进

在懒汉模式的getInstance方法中,存在赋值(修改)操作,在多线程中就可能会线程安全问题,我们来举个例子

由于线程是随机调度,抢占式执行的,当t1线程执行到任何一个指令,t2线程都有可能抢占到cpu上继续执行,图中只是其中一种执行顺序,还会有其他执行顺序的可能。按照图中的执行顺序来看,t1线程和t2线程都分别创建了一个实例(都进行了new操作),当实例对象的内存数据较大时,多加载一次内存数据会有很大的时间开销

那么如何改进代码呢?

1.使用synchronized关键字,将if和new打包成一个原子操作,

2.由于只有第一次调用getInstance方法才会存在线程安全问题,一旦实例创建完毕,在后续调用中进行的只是读操作,不存在线程安全问题,这时进行加锁,就有点多余了,并且加锁操作本身也有一定开销(可能会使线程阻塞),所以需要添加一个if语句判断是否需要加锁

3.在两个if语句之间,synchronized会使线程阻塞,在阻塞过程中其他线程可能会修改instance的值,由于内存可见性问题,该线程可能没有感知到,所以需要给instance变量添加volatile关键字

4.可能会出现指令重排序问题,也需要给instance变量添加volatile关键字

public class Singleton1 {
    private static volatile Singleton1 instance = null;
    private static Object locker = new Object();
    public static Singleton1 getInstance() {
        if(instance == null) {//判定是否要加锁
            //实例化之前需要加锁
            // 实例化之后,线程已经安全,无需加锁
            synchronized (locker) {
                if (instance == null) {
                    instance = new Singleton1();
                }
            }
        }
        return instance;
    }
    private Singleton1(){

    }
}
class Lazy{
    Singleton1 singleton1 = Singleton1.getInstance();
    Singleton1 singleton2 = Singleton1.getInstance();
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值