这么写才最实用 单例模式+多线程

目录

了解什么是单例模式

什么时候使用单例模式,有什么优缺点

单例的两种基本模式

进阶——给单例加锁

屏障问题


了解什么是单例模式

单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)

什么时候使用单例模式,有什么优缺点

1.类 加载到内存后,需实例化一个单例,JVM保证线程安全

2.当某些情况下,我们只需要new一个对象

3.spring接管对象的时候默认就是单例的

4.优点:简单实用

5.缺点:不管是否用到,类加载的时候都完成实例化

单例的两种基本模式

饿汉式单例——

/**
 * @description: 最简单的单例模式----饿汉式
 * @author: jd
 * @date: 2022年2月27日
 */
public class Mgr01 {
    //一开始自己new一个 但是是私有方法  只能自己new  
    private static final Mgr01 INSTANCE = new Mgr01();

    //构造函数
    private Mgr01(){};
    
    //如果别人想用的话  提供一个方法
    public static Mgr01 getInstance(){
        return INSTANCE;
    }
    public void m(){System.out.println("m");}

	//作为客户端  不管调用多少次 都是同一个
    public static void main(String[] args) {
        Mgr01 m1 = Mgr01.getInstance();
        Mgr01 m2 = Mgr01.getInstance();
        System.out.println(m1==m2);
    }
}

问题一:
此模式为饿汉模式  不管我用不用这个对象都会提前new一个出来  占用内存
解决办法:  懒汉式单例

懒汉式单例——

/**
 * 达到了按需初始化的目的 但是带来了线程不安全
 */
public class Mgr02 {
    private static Mgr02 INSTANCE;

    private Mgr02(){

    }
    public static Mgr02 getInstance(){
        // 先判断 INSTANCE是否为空 如果为空再去new
        if (INSTANCE == null){
            try {
                Thread.sleep(1);
            }catch (InterruptedException e){
                    e.printStackTrace();
            }
            INSTANCE = new Mgr02();
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(() -> System.out.println(Mgr02.getInstance().hashCode())).start();
        }
    }
}

问题二:当多线程的环境下  不能保证对象只有一个
 假设当第一个线程进行判断的时候  判断为空 还没开始newINSTANCE 这时候 暂停切换第二个线程  第二个线程判断也为空  然后new了一个 INSTANCE  然后切换回第一个线程 接着又new了一个  INSTANCE  当线程无限多的时候  就有可能会new很多出来 造成线程不安全 程序就不是单例了

解决:加锁

进阶——给单例加锁

加锁式单例——

/**
 * @description: 加锁式单例   synchronized
 * @author: jd
 * @date: 2022年2月27日
 */
public class Mgr03 {

    private static Mgr03 INSTANCE;

    private Mgr03(){

    }
    public static synchronized Mgr03 getInstance(){
        /**
         * 假设这里是业务逻辑
         */
        if (INSTANCE == null){
            try {
                Thread.sleep(1);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            INSTANCE = new Mgr03();
        }
        return INSTANCE;
    }
    public void m () {System.out.println("m");}
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(() -> System.out.println(Mgr02.getInstance().hashCode())).start();
        }
    }
}


// synchronized  上锁  
/*
*  过程:1. 首先有一把锁 当第一个线程抢到锁以后把锁锁上 完成逻辑后 在把锁打开 其他线程才能进入
*/

问题三:当我们的方法包含了一些太复杂的业务逻辑的时候而且这些业务逻辑不涉及到读写 我们没有必要将这些业务逻辑也锁进去  这样称为锁的粒度太粗了

DCL(Double Check Lock)双重校验锁式单例——

/**
 * @description: 双重锁+单例
 * @author: jd
 * @date: 2022年2月27日
 */
public class Mgr05 {
    private static volatile Mgr05 INSTANCE;

    private Mgr05(){

    }
    public static  Mgr05 getInstance(){
        /**
         * 假设这里是业务逻辑
         */
        if (INSTANCE == null){  //Double Check Lock
            //双重检查
            synchronized (Mgr05.class){
                if (INSTANCE ==null) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    INSTANCE = new Mgr05();
                }
            }
        }
        return INSTANCE;
    }
    public void m () {System.out.println("m");}
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(() -> System.out.println(Mgr02.getInstance().hashCode())).start();
        }
    }
}



这里为什么要有两重判断呢  直接加锁判断不就行了——

主要是为了性能--一旦进行判断后发送已经有了实例了  就减少了竞争锁的过程

屏障问题

屏障问题  在CPU底层执行汇编语言的时候,有的语言是可以进行顺序互换的  这时候就有问题了    在Java中  如果想要语言在执行的时候不可以顺序互换  需要怎么办呢?

答案是使用volatile
volatile修饰的内存 不可以重排顺序  对volatile修饰变量的读写访问  都不可以换顺序

猜你喜欢

转载自blog.csdn.net/weixin_44693109/article/details/123163732