什么是单例模式以及单例模式的几种实现

什么是单例模式?

单例模式,是一种常用的软件设计模式。通过单例模式可以保证系统中,应用该模式的这个类只有一个实例,即一个类只有一个对象实例。下面讲讲他的四种实现。

1. 饿汉式单例设计模式

饿汉单例设计模式就是使用类的时候已经将对象创建完毕,不管以后会不会使用到该实例化对象,先创建了再说。很着急的样子,故被称为“饿汉模式”。

代码实现:

package com.hy.practice;

/**
 * @author HY
 * @ClassName UserService1
 * @Description 饿汉式
 * @DateTime 2020/12/27 10:54
 * Version 1.0
 */
public class UserService1 {
    // 1. 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
    private UserService1() {

    }
    // 2. 在该类内部产生一个唯一的实例化对象
    private static UserService1 userService1=new UserService1();

    // 3. 定义一个静态方法返回这个唯一对象。
    public static UserService1 getInstance() {
        return userService1;
    }
}

测试:

UserService1 instance1 = UserService1.getInstance();
        UserService1 instance2 = UserService1.getInstance();
        UserService1 instance3= UserService1.getInstance();
        System.out.println(instance1);
        System.out.println(instance2);
        System.out.println(instance3);

输出:
在这里插入图片描述

2.懒汉式单例设计模式

懒汉单例设计模式就是调用getInstance()方法时实例才被创建,先不急着实例化出对象,等要用的时候才实例化出对象。不着急,故称为“懒汉模式”。

package com.hy.practice;

import sun.security.jca.GetInstance;

/**
 * @author HY
 * @ClassName UserService2
 * @Description 懒汉式
 * @DateTime 2020/12/27 10:56
 * Version 1.0
 */
public class UserService2 {
    // 1. 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
    private UserService2() {
    }
    // 2. 在该类内部产生一个唯一的实例化对象
    private static UserService2 userService2;

    // 3. 定义一个静态方法返回这个唯一对象。
    public static UserService2 getInstance() {
        if (userService2==null){
            return userService2=new UserService2();
        }
        return userService2;
    }
}

测试:

UserService2 instance1 = UserService2.getInstance();
        UserService2 instance2 = UserService2.getInstance();
        UserService2 instance3 = UserService2.getInstance();
        System.out.println(instance1);
        System.out.println(instance2);
        System.out.println(instance3);

输出:
在这里插入图片描述
但是这种实现方式在多线程的情况下会创建多个实例,无法保证单一实例,开两个线程打debug验证一下:
开两个线程获取实例

 		new Thread(()->{
            UserService2 instance = UserService2.getInstance();
            System.out.println(instance);
        }).start();
        new Thread(()->{
            UserService2 instance = UserService2.getInstance();
            System.out.println(instance);
        }).start();

在getInstance()的方法中打个断点
第一个线程:
在这里插入图片描述
进入if:
第一个线程
第一个线程进入if语句中还未new出实例的时候,cpu调度被另一个线程抢走,另一个线程判断为null也会进入if语句:
第二个线程:
在这里插入图片描述
进入if:
在这里插入图片描述
让程序执行完,可以看到输出了两个不同的对象,这就出问题了:
在这里插入图片描述
所以可以在方法上用一个synchronized解决,但是这样做的效率太低了,当有很多个线程调用此方法的时候会造成一个线程执行,其他线程Blocked(锁阻塞)。所以有第二个版本:

3.双重检测锁

双重检查锁是高级版的懒汉式实现。先讲讲他的实现思路:当多个线程调用getInstance()方法时,之前的版本是在方法上加锁,所有没有拿到锁的线程都会锁阻塞,那有没有办法让其他线程也能调用方法?有,只在实例化对象的地方才加锁。

public static UserService3 getInstance() {

        if (userService3 == null) {
            synchronized (UserService3.class) {
                userService3 = new UserService3();
            }
        }
        return userService3;
    }

但是仔细想想,这样加锁好像跟没加锁有一样问题啊,只是把实例化对象的步骤锁住了,当前线程在执行完if语句判断后cpu调度被抢走,其他线程判断为空,也会进入if中,只是“排队”new出实例罢了。如图:在这里插入图片描述
所以有了最终版本!再判断一次,这就是双重检测锁。

package com.hy.practice;

/**
 * @author HY
 * @ClassName UserService3
 * @Description TODE
 * @DateTime 2020/12/27 15:37
 * Version 1.0
 */
public class UserService3 {
    //构造私有化
    private UserService3() {

    }
    //维护一个唯一的对象
    private static UserService3 userService3;

    public static  UserService3 getInstance() {

        if (userService3==null){
            synchronized (UserService3.class) {
                //再判断一次
                if (userService3==null){
                    userService3 = new UserService3();
                }
            }
        }
        return userService3;
    }

}

验证一下

for (int i = 0; i < 100; i++) {
            new Thread(()->{
                System.out.println(UserService3.getInstance());
            }).start();
        }

结果:太长了我就不贴图了,目测正确。大家可以自己试一下,也可以选择相信我~

4.静态内部类

最优雅的方式,因为静态内部类有以下特点:

  • 外部类初次加载,会初始化静态变量、静态代码块、静态方法,但不会加载内部类和静态内部类。
  • 可以使用外部类的私有构造方法
  • 静态内部类只加载一次

所以,我们可以利用静态内部类的类加载特点,实现单例模式。

package com.hy.practice;

import sun.security.jca.GetInstance;

/**
 * @author HY
 * @ClassName UserService
 * @Description 静态内部类
 * @DateTime 2020/12/27 10:45
 * Version 1.0
 */
public class UserService {
    //构造私有化
    private UserService() {
    }

    private static class InnerClass {
        //内部类内实例化
      static  UserService userService=new UserService();
    }

    public static UserService getInstance(){
        return InnerClass.userService;
    }

}

测试一下:

for (int i = 0; i < 100; i++) {
            new Thread(()->{
                System.out.println(UserService.getInstance());
            }).start();
        }

没毛病:
在这里插入图片描述

总结

  • 第一种饿汉式:优点是线程安全,缺点是无法文现懒加载
  • 第二种懒汉式:优点是实现了懒加载,缺点是执行效率低
  • 第三种双重检测锁:优点即实现了懒加载、线程又安全、执行效率还比较高,只有第一次执行获取单例对象的方法需要加锁,之后有对象了都不用加锁
  • 第四种方式静态内部类:线程又安全、又实现了懒加载、还不用加锁
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值