设计模式之单例模式

个人主页: 不漫游-CSDN博客

目录

知识铺垫

饿汉模式

懒汉模式

懒汉模式的线程安全问题

加锁

重复加锁对性能的影响 

指令重排序问题


 

知识铺垫

什么是设计模式?

通俗点讲就类似于“棋谱”,有一些固定的套路,可以针对一些特殊场景给出比较好的解决方案。

而在开发过程中,便可以起到模版的作用。

什么是单例模式?

单例模式能保证某个类在程序中只存在唯⼀⼀份实例,⽽不会创建出多个实例。

⽐如JDBC中的DataSource实例就只需要⼀个。--->http://t.csdnimg.cn/IQ3cj

主流的单例模式有以下两种写法--->

饿汉模式,顾名思义,就像一个总是急着吃东西的“饿汉”一样,不管是不是马上需要用到它,就是提前创建好单例实例。

懒汉模式则是延迟创建单例实例,直到真的需要用到它的时候才去创建,就像一个总是等到最后一刻才行动的“懒汉”一样。

举个例子,每次吃完饭后,对于洗碗这件事,有些人就立刻把碗给洗干净。(饿汉模式)

另外有些人则是放水里泡着,等到下一顿饭需要碗装菜的时候再洗。要装几个菜才洗几个碗。(懒汉模式)

饿汉模式

class Singleton {
    //在类加载时就创建单例实例
    private static final Singleton instance = new Singleton();

    //提供一个公共的访问方法,返回单例实例
    public static Singleton getInstance() {
        return instance;
    }

    //私有构造方法,防止外部实例化
    private Singleton() {
    }
}

 仔细观察,instance成员变量就是“类成员”,一开始就创建好,虽然还没人用,并且讲构造函数设为私有,只能通过访问getInstance()方法去获取instance。

这也就体现了单例模式的特点---确保不会随意new一个其他对象出来。

而且通过以下代码验证:单例模式确保了在整个应用程序中只有一个实例存在。

public class demo21 {
    public static void main(String[] args) {
        //获取单例实例
        Singleton dog1 = Singleton.getInstance();
        Singleton dog2 = Singleton.getInstance();

        //检查两个实例是否相同
        if (dog1 == dog2) {
            System.out.println("1");
        } else {
            System.out.println("2");
        }
    }
}

因此无论调用多少次 ​getInstance​方法,返回的都是同一个实例,所以结果如图:

 

 

懒汉模式

class LazySingleton {
    //声明一个静态的实例变量,但不立即创建实例
    private static LazySingleton instance;

    //私有构造方法,防止外部实例化
    private LazySingleton() {
    }

    //提供一个公共的静态方法,返回单例实例
    public static LazySingleton getInstance() {
        //如果实例还未创建,则创建它
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

和饿汉相比,懒汉模式需要用到的时候才会创建实例,即推迟了实例的创建。

所以不会一开始就new。只有在需要访问instance 的时候,才会创建实例。

其他和饿汉大同小异。加以验证。

public class demo22 {
    public static void main(String[] args) {

        SingleLazy dog1 = SingleLazy.getInstance();
        SingleLazy dog2 = SingleLazy.getInstance();

        if (dog1 == dog2) {
            System.out.println("1");
        } else {
            System.out.println("2");
        }
    }
}

懒汉模式的线程安全问题

这里只谈论懒汉模式,因为饿汉模式不存在线程安全问题---没有涉及到线程的切换。

忘记了线程安全问题的相关知识点可以回顾一下-->http://t.csdnimg.cn/QC4er

仔细观察懒汉模式下的代码。

class LazySingleton {
    //声明一个静态的实例变量,但不立即创建实例
    private static LazySingleton instance;

    //私有构造方法,防止外部实例化
    private LazySingleton() {
    }

    //提供一个公共的静态方法,返回单例实例
    public static LazySingleton getInstance() {
        //如果实例还未创建,则创建它
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

这里有个逻辑是先判断再创建实例,但这两者之间可能会涉及到线程的切换,从而引发问题。

比如有2个线程t1,t2.

首先有一点是线程是随机调度,抢占式执行的。

在t1线程执行判断语句的时候, 就可能跳到t2线程,也符合条件,创建了一个实例。

但是回到t1线程也会创建一个新的实例(符合判断条件),所以就会覆盖第一次的实例。而原本的实例就会被回收掉了。

加锁

原因可以总结成if和new之间出现了线程切换。因此可以把它们打包成一个整体。

    public static SingleLazy getInstance() {
 
            synchronized (lock1) {
                //判断是否创建实例对象
                if (instance == null) {
                    instance = new SingleLazy();
                }
            }
     
        return instance;
    }

重复加锁对性能的影响 

但也有新的问题,线程是安全了,但 instance一旦创建好之后,if语句不再执行。

但每次调用getInstance()去访问的时候,都会触发加锁操作。这样便可能引起堵塞,影响性能。

即还要判断一下要加锁的时候加锁,可以不用加锁的时候就不加锁。如图:

    public static SingleLazy getInstance() {
        //判断是否加锁
        if (instance == null) {
            synchronized (lock1) {
                //判断是否创建实例对象
                if (instance == null) {
                    instance = new SingleLazy();
                }
            }
        }
        return instance;
    }

看到这里是不是很懵?怎么会有两个一模一样的if(instance==null)?

但只是写法相同,实际作用则不同。

第一个if语句判断是否要加锁。因为刚刚分析过,已经创建好实例的话就没必要次次调用getInstance()去访问的时候,都触发加锁操作。

第二个if 语句则是判断是否要创建实例对象。这个就是最开始的if语句。

指令重排序问题

到这里看似没有问题了,但想想创建一个实例对象需要几步?

1.分配内存空间。
2.初始化对象。
3.将引用指向分配的内存空间。

举个栗子~

想象你正在准备开一家新店。这个过程可以分为三个主要步骤:

1. 选址:这就像是在内存中为你的对象“分配空间”。你找到了一个合适的地方。

2. 装修:这就像是“初始化对象”。你开始装修店铺。

3. 开门营业:这就像是“将引用指向分配的内存空间”。你完成了所有的准备工作,现在人们可以通过地址找到你的店铺,进来购物。在编程中,这个“地址”就是对象的引用,告诉程序去哪里找到这个对象。

但在多线程环境中,顺序可能发生调换,可能是1 3 2 的执行顺序。这样一来,又可能导致线程切换的问题。

所以安全起见,还要加上volatile关键字。

class SingleLazy {
    private static final Object lock1=new Object();

    //使用volatile关键字
    private static volatile SingleLazy instance;

    //私有构造函数,防止外部实例化
    private SingleLazy() {}

    public static SingleLazy getInstance() {
        //判断是否加锁
        if (instance == null) {
            synchronized (lock1) {
                //判断是否创建实例对象
                if (instance == null) {
                    instance = new SingleLazy();
                }
            }
        }
        return instance;
    }
}

同样加以验证

public class demo22 {
    public static void main(String[] args) {

        SingleLazy dog1 = SingleLazy.getInstance();
        SingleLazy dog2 = SingleLazy.getInstance();

        if (dog1 == dog2) {
            System.out.println("1");
        } else {
            System.out.println("2");
        }
    }
}

 

看到最后,如果觉得文章写得还不错,希望可以给我点个小小的赞,您的支持是我更新的最大动力

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值