博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
零基础也能看懂的java设计模式---单例模式
阅读量:121 次
发布时间:2019-02-25

本文共 5023 字,大约阅读时间需要 16 分钟。

一、单例模式介绍

1.什么是单例模式

所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。

2.单例模式的使用场景

需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数 据库或文件的对象(比如数据源、session工厂等)

3.评价单例模式方法的指标
  • 是否是单例模式?
  • 是饿汉式还是懒汉式?
  • 是否是线程安全的?
  • 效率如何?
4.实现单例模式的几种方法

具体看下文

二、饿汉式

饿汉式,实例在初始化的时候就已经建好了,不管你有没有用到,都先建好了再说。好处是没有线程安全的问题,坏处是浪费内存空间。

1.使用静态常量的方式实现
/** * @author 四五又十 * @version 1.0 * @date 2020/6/1 19:50 */public class SingleTon01 {
//1. 构造器私有化, 外部能new private SingleTon01() {
} //2.本类内部创建对象实例 private static final SingleTon01 instance = new SingleTon01(); //3.提供一个公有的静态方法,返回实例对象 public static SingleTon01 getInstance(){
return instance; }}

分析:使用静态常量的方式来创建,由于在类加载的时候创建该类的实例,所以不会有线程安全问题,也就是说是线程安全的,但是只要这个类一进行装载,那么该类就完成了实例化,也就是没有达到懒加载的目的。

优点:写法比较简单,就是在类装载的时候就完成实例化。避免了线程同 步问题

缺点:只要这个类一进行装载,那么该类就完成了实例化,这样有可能造成内存浪费

2.静态代码块实现

这种方式与上面就是表现形式不一样,其他都一样

/** * @author 四五又十 * @version 1.0 * @date 2020/6/1 19:50 */public class SingleTon02 {
//1. 构造器私有化, 外部能new private SingleTon02() {
} //2.本类内部创建对象实例 private static SingleTon02 instance; static {
instance = new SingleTon02(); } //3.提供一个公有的静态方法,返回实例对象 public static SingleTon02 getInstance(){
return instance; }}

三、懒汉式

1.有线程安全问题的实现
/** * @author 四五又十 * @version 1.0 * @date 2020/6/1 19:50 */public class SingleTon03 {
//1. 构造器私有化, 外部能new private SingleTon03() {
} private static SingleTon03 singleTon03; public static SingleTon03 getInstance(){
if(singleTon03 == null){
singleTon03 = new SingleTon03(); } return singleTon03; }}

分析:当需要使用该类时,getInstance()方法会判断当前对象类中是否有singleTon03的实例,如果没有那么创建,如果有直接返回,这样似乎做到了单例和懒加载,即需要时才进行加载;但是仔细一想,在多线程的环境下,如果其中A线程执行到了if判断并且通过了判断,然而此时切换到了B线程,那么显然B线程也是可以通过判断的那么会创建singleTon03的实例,如果此时切换回了A线程,那么A也会再次创建该对象的实例,这样就不是单例的效果!

所以这种方法不允许使用

2.加synchronized锁解决线程安全问题

那么很显然,要解决上述的问题,只需要在上述方法上加上synchronized锁即可:

/** * @author 四五又十 * @version 1.0 * @date 2020/6/1 19:50 */public class SingleTon04 {
//1. 构造器私有化, 外部能new private SingleTon04() {
} private static SingleTon04 singleTon03; public static synchronized SingleTon04 getInstance(){
if(singleTon03 == null){
singleTon03 = new SingleTon04(); } return singleTon03; }}

分析:当A,B线程想要同时执行getInstance方法时,由于加上了synchronized锁,那么其中一个线程只能等待,等轮到该等待的线程时,此时已经不能通过if判断,那么这样即使在多线程环境下也可以做到单例;synchronized是一个重量级的锁,很显然会带来效率问题

优点:解决了线程不安全问题

缺点: 效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行 同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例, 直接return就行了。方法进行同步效率太低

3.解决效率过低:Double-Check

这时,为了解决效率多低的问题,想当然的会想到一种方式:

/** * @author 四五又十 * @version 1.0 * @date 2020/6/1 19:50 */public class SingleTon05 {
//1. 构造器私有化, 外部能new private SingleTon05() {
} private static SingleTon05 singleTon03; public static SingleTon05 getInstance(){
if(singleTon03 == null){
synchronized(SingleTon05.class){
singleTon03 = new SingleTon05(); } } return singleTon03; }}

也就是在代码块上加上synchronized锁

那么,这样到底有没有解决问题呢?没有!当线程A执行到if判断并且通过if判断时,此时如果线程B也执行if判断那么显然结果和第一种具有线程安全的实现是一样的,也就是没有解决线程安全问题。

为了要解决这个问题,提出了双重验证的方法,具体如下:

/** * @author 四五又十 * @version 1.0 * @date 2020/6/1 19:50 */public class SingleTon06 {
//1. 构造器私有化, 外部不能new private SingleTon06() {
} private static volatile SingleTon06 singleTon06; public static SingleTon06 getInstance(){
if(singleTon06 == null){
synchronized(SingleTon06.class){
if(singleTon06 == null){
singleTon06 = new SingleTon06(); } } } return singleTon06; }}

分析:还是按照上面分析的思路,如果线程A,B同时进入if判断并且通过,那么先进入到if里面的线程进行里面的方法时,则会被加上一把synchronized锁,当执行完成创建好了该实例后,另外一个线程进入if里面,并且也执行加上了一把synchronized锁的代码,但是此时却是不能通过第二个if判断,因为singleTon06已经被先执行的进程创建好了,那么这样就实现了多线程下的安全问题,也同时大大提高了效率!

优点:线程安全;延迟加载;效率较高

在实际开发中,建议使用

volatile是一个轻量级的锁,主要有两个作用:

  1. 被volatile修饰的变量保证对所有线程可见

    2.禁止指令重排序优化

在这里,关键字volatile的作用主要是禁止指令重排序优化。首先先理解在执行new SingleTon06()中,CPU大概干了些什么事情:

1.看class对象是否加载,如果没有就先加载class对象;

2.分配内存空间,初始化实例;

3.调用构造函数;

4.返回地址给引用

但是在实际的过程中,CPU为了优化程序,可能会进行指令重排序,打乱这3,4这几个步骤,导致实例内存还没分配,就被使用了。

线程A执行到new Singleton06(),开始初始化实例对象,由于存在指令重排序,这次new操作,先把引用赋值了,还没有执行构造函数。这时时间片结束了,切换到线程B执行,线程B调用new Singleton06()方法,发现引用不等于null,就直接返回引用地址了,然后线程B执行了一些操作,就可能导致线程B使用了还没有被初始化的变量,所以要加上volatile禁止CPU进行指令重排

4.使用静态内部类
/** * @author 四五又十 * @version 1.0 * @date 2020/6/1 19:50 */public class SingleTon07 {
//1. 构造器私有化, 外部能new private SingleTon07() {
} private static class singleTon07Instance{
private static final SingleTon07 INSTANCE = new SingleTon07(); } public static SingleTon07 getInstance(){
return singleTon07Instance.INSTANCE; }}

要理解这种方式首先需要明确一点:

静态内部类不持有外部类的引用(《java核心技术》)

也就是说当外部类加载时,静态内部类并不会随之加载,那么这样利用类加载机制保证了懒加载!并且这个过程是线程安全的

四、使用枚举

/** * @author 四五又十 * @version 1.0 * @date 2020/6/1 19:50 */public enum  SingleTon08 {
INSTANCE; public void method(){
}}

利用jdk1.5增加的枚举可以很好的实现单例,线程安全,懒汉式

转载地址:http://lyf.baihongyu.com/

你可能感兴趣的文章