首页 > 其他 > 详细

设计模式03-单例模式

时间:2020-04-29 21:05:50      阅读:75      评论:0      收藏:0      [点我收藏+]

1.2.1.设计模式03-单例模式

时长:1h33min

课程目标

》单例模式的应用场景

》IDEA下的多线程调试方式

》保证线程安全的单例模式策略

》掌握反射暴力攻击单例解决方案及原理分析

序列化破坏单例的原理及解决方案

》掌握常见的单例模式写法

原型模式的应用场景及常用写法

学习目的

  》深入掌握单例模式

  》解决面试题

1.单例模式的定义

  单例模式,Singleton Pattern.

定义:

  是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。

  隐藏所有的构造方法【私有】

  创建创建型模式。

1.1.适用场景

  确保在任何情况下都只有一个实例。

 

生活中单例体现:

  国家主席,公司ceo,部门经理。

代码中单例类:

  ServletContext,ServletConfig,ApplicationContext,DBPool

为什么它是单例的呢?

  如:配置文件,只能有一个,否则该使用谁呢。【唯一性主权】

2.单例模式常见用法

1.饿汉式单例

2.懒汉式单例

3.注册式单例

4.ThreadLocal单例

2.1.饿汉式单例

  人很饿------》着急吃饭---》在类首次加载时创建实例【用不用,先吃饱再说】

2.1.1.代码示例
package com.wf.singleton;

/**
 * @ClassName HungrySingleton
 * @Description 饿汉式单例
 * @Author wf
 * @Date 2020/4/29 17:28
 * @Version 1.0
 */
public class HungrySingleton {
    //首次加载静态成员
    public static final  HungrySingleton singleton = new HungrySingleton();
    //构造私有
    private HungrySingleton(){}
    //全局访问点
    public static HungrySingleton getInstance(){
        return singleton;
    }
}

 

第二种写法:

package com.wf.singleton;

/**
 * @ClassName HungryStaticSingleton
 * @Description 饿汉式单例---静态块初始化
 * @Author wf
 * @Date 2020/4/29 17:31
 * @Version 1.0
 */
public class HungryStaticSingleton {
    //final保证反射不会改变实例
    private static final HungryStaticSingleton instance;
    private HungryStaticSingleton(){}
    static{
        instance = new HungryStaticSingleton();
    }
    public static HungryStaticSingleton getInstance(){
        return instance;
    }
}
2.1.2.优缺点总结

优点:

  执行效率高,性能高【无锁】

缺点

  可能生成内存浪费,因为实例不被使用,也进行new .

说明:

  这种写法,只能在小范围内使用。

 

2.2.懒汉式单例

  人很懒------不着急-----等到使用时再创建

2.2.1.示例代码
package com.wf.singleton;

/**
 * @ClassName LazySimpleSingleton
 * @Description 懒汉式单例
 * @Author wf
 * @Date 2020/4/29 17:43
 * @Version 1.0
 */
public class LazySimpleSingleton {
    private static LazySimpleSingleton instance;
    private LazySimpleSingleton(){}

    public static LazySimpleSingleton getInstance(){
        if(null == instance) {
            instance = new LazySimpleSingleton();
        }
        return instance;
    }
}
2.2.2.总结

优点:

  节省内存空间,使用时再创建实例。

缺点:

  线程不安全。性能差。

  为什么会线程不安全呢?

  线程不安全来自于getInstance方法,方法的调用者最终归结到线程。

当出现多个线程,同时调用getInstance方法时,线程间会抢占资源。如下所示:

技术分享图片

 

 

 这是理论上的分析。下面在idea中进行代码调试,说明这种线程不安全性。

A.多线程调试线程安全性

【1】创建测试类

package com.wf.singleton;

import org.junit.Test;

public class LazySimpleSingletonTest {

    @Test
    public void getInstance() {
        Thread t1 = new Thread(new ExecutorThread());
        Thread t2 = new Thread(new ExecutorThread());
        t1.start();
        t2.start();
        System.out.println("end");
    }
}

【2】测试任务类

package com.wf.singleton;

/**
 * @ClassName ExecutorThread
 * @Description 创建一个测试task
 * @Author wf
 * @Date 2020/4/29 18:53
 * @Version 1.0
 */
public class ExecutorThread implements Runnable {
    @Override
    public void run() {
        LazySimpleSingleton instance = LazySimpleSingleton.getInstance();
        System.out.println(Thread.currentThread().getName() +":"+instance);
    }
}

在任务类中,run方法,访问单例类,获取实例。并打印实例hash地址。当两个线程,打印hash地址相同时,

说明只有一个实例,是单例的。

不存在线程安全问题,否则,是线程非安全的。

B.调试过程

【1】直接运行测试类,查看结果:

技术分享图片

 

明显,可以看到,hash地址不同,创建现两个实例。

 技术分享图片

 

多次运行,可以发现,有时只会创建一个实例。这时,线程执行的随机性决定的。   

我们可以得出结论,getInstance方法是线程非安全的。

【2】断点调试之前,理论上结果分析

从上面的运行,有两种结果:

》同一实例

  a,两个线程顺序执行

  b.两个线程都进入getInstance方法,if分支【后者覆盖前者

    第一个线程,创建出一个实例instance1

    第二个线程,稍后第二个线程,也进入if,并执行new实例instance2,instance2就把instance1给覆盖掉了

    最后,得到的实例都是instance2

》不同实例

  同时进入if条件,按顺序返回

  第一个线程new后,快速执行结束方法,打印实例1

  第二个线程,再次new,打印实例2

【3】断点调试

断点设置如下:

技术分享图片

 

 技术分享图片

 

技术分享图片

 

注意:

  3个断点,都设置成Thread多线程运行模式。设置方法:右击断点,弹出设置框,切换到Thread模式,然后Done.

 

然后,debug运行测试类:

技术分享图片

 

在Frames窗口下,可以查看到所有的线程。现在,几个线程都是running状态,表示线程都抢到cpu资源,但是什么时候,

执行任务,由cpu来决定。

切换到Thread-0,然后执行下一步,先让它按正常状态执行。

技术分享图片

Thread-0线程,先顺序执行完成,并打印.

技术分享图片

 

  然后,切换到第二个线程Thread-1,让他进入getInstance方法,经过if判断,由于instance已经创建,非null,条件不成立。

所以,两个线程创建一个实例。如下所示:

技术分享图片

技术分享图片

技术分享图片

 

可以看到,最后打印相同的结果。

 

第二种情况 :调试后者覆盖前者

先让两个线程都进入if分支,如下所示:

Thread-0线程进入:

技术分享图片

 Thread-1线程进入:
技术分享图片

 

然后,再切换到Thread-0,先执行new语句,如下所示:

技术分享图片

 

再切换到,线程1,执行new操作,如下所示:

技术分享图片

 

再切换到,Thread-0执行完成,打印结果。

技术分享图片

 

 再让Thread-1执行完成,打印结果:

 技术分享图片

 

 

 第三种情况:不同实例

首先,让两个线程都进入getInstance方法的if分支。

技术分享图片

 

技术分享图片

 

然后,先让Thread-0线程执行完,并打印。

技术分享图片

 

最后,Thread-1线程,再执行完成,并打印,如下所示:

技术分享图片

 

技术分享图片

 

出现不同的实例。

2.2.3.解决线程不安全

解决方案:

使用sycronized关键字,加同步锁,如下所示:

package com.wf.singleton;

/**
 * @ClassName LazySimpleSingleton
 * @Description 懒汉式单例
 * @Author wf
 * @Date 2020/4/29 17:43
 * @Version 1.0
 */
public class LazySimpleSingleton {
    private static LazySimpleSingleton instance;
    private LazySimpleSingleton(){}

    public synchronized static LazySimpleSingleton getInstance(){//使用synchronized解决线程安全性问题
        if(null == instance) {
            instance = new LazySimpleSingleton();
        }
        return instance;
    }
}

问题解决了。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  

 

 

 

 

  

3.注册式单例

4.ThreadLocal单例

附录:

idea自动生成junit测试类:

两种快捷键:

1.在要生成测试类的类里面,按ctrl+shift+t –> create new test

  

 

设计模式03-单例模式

原文:https://www.cnblogs.com/wfdespace/p/12804435.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!