单例模式指强制要求某个类,在某个程序中只有唯一实例(对象),不允许创建多个实例。单例模式思想的基本分类:1.饿汉思想【类加载的时候就创建出来,类中创建的对象外面可获取,尽早创建,属于提前初始化】;2.懒汉思想【尽量晚的创建实例,第一次调用函数时,才new 对象,属于延迟初始化】;这两个基本分类延伸出多种单例模式的写法,不过饿汉思想只有2种写法,其余全是懒汉思想延伸出去的,这是由于饿汉方式是天生线程安全的,但基本的懒汉方式是线程不安全的,为了优化它,人们想出不同的写法,去保证懒汉思想下线程安全。接下我将分别介绍这些写法。

一、饿汉思想的写法

        1.静态常量饿汉写法:

//1.静态常量饿汉写法
public class Single{
    static final Single sg=new Single();//final修饰静态引用变量是指这个变量的引用地址,不能指向                            其它对象
    private Single(){}
    public static Single getInstance(){
        return sg;
        }
}

        2.静态代码块饿汉写法:

2.静态代码块的饿汉写法
    public class Single{
    private static Single instance;
    static{
        instance=new Single();
    }
    private Single(){
        }
    public static Single getInstanse(){
        return instance;

    }

  }

以上是饿汉思想的所有写法。

二、懒汉思想的写法

        1.基本的懒汉思想的写法【线程不安全】:

//懒汉思想的写法
//1.线程不安全基本写法
public class lazyman {
    private static lazyman instance;
    private lazyman(){
    }
    public static lazyman getInstance(){
        if(instance==null){
            instance=new lazyman();
        }
        return instance;
    }
}

面试(饿汉、懒汉思想是否线程安全,若不是该咋办?):在多线程环境下调用,饿汉式对象在类加载时就创建好了,之后所有线程只是读取,不会再创建,jvm类加载机制会保证只创建一次对象,懒汉式思想是线程不安全的,一个是因为对象是第一次调用getInstance()才会被创建,多线程同时调用,会同时进入if(instance==null),导致创建多个对象,单例失败,另一个因为是多个线程对同一静态变量instance做赋值操作,写竞争、线程不安全,最终还可能被后面的对象覆盖,浪费创建的开销。如何解决?1.加锁,将条件与修改加锁打包成原子操作,后执行的线程会在此处阻塞,当它进入时,前面的线程早就是对象不为null,自然就无法重新创建对象,但若未出现线程安全问题时,每调用一次方法,就加锁相互阻塞,影响程序执行效率,于是我们采用2.按需加锁,真正涉及到线程安全问题再加锁,不涉及就不加,在加锁前加入一个条件判断,判断实例是否被创建,未创建才加锁。还有两个问题就是内存可见性问题(线程改了变量instance没写入主内存但其它线程看不见误以为还是空)、指令重排序问题(new的过程中指令重排序,导致对象半初始化 ,即先分配内存,把地址赋给instance但还没初始化对象  )解决方法是3.给对象加volatile,确保每次读取操作,都是读主内存与此变量的读取与修改不会触发指令重排序。对于懒汉思想还有一种天生线程安全的写法是4.使用静态内部类,在静态内部类中创建静态常量对象

        2.加锁将条件与修改打包为原子操作写法——优化2.0版(仍有问题)

2.将条件判断与修改打包成原子操作1.0版
public class lazyman{
    private static lazyman instance;
    private lazyman(){

    }
    public static synchronized lazyman getInstance(){
        if(instance==null){
            instance=new lazyman();
        }
        return instance;
    }

}

        3.按需加锁(进入加锁前先进行条件判断)【双重检查锁DCL】写法——优化3.0版(仍有问题)

public class lazyman {
    private static  lazyman instance;
    private  lazyman(){

    }
    public static  lazyman getInstance(){
        if(instance==null){
            synchronized (lazyman.class){//类对象,全局只有一个,每一个类,jvm都会唯一创建一个对象来表示它
                if(instance==null){
                    instance=new lazyman();
                }
            }
        }
        return  instance;
    }
}

        4.给对象加volatile解决内存可见性问题与指令重排序问题写法——优化4.0版(不优雅但完整)

//4.加volatile修饰对象
public class lazyman {
    private  static volatile lazyman instance;
    private  lazyman(){
        
    }
    public static  lazyman getInstance(){
        if(instance==null){
            synchronized (lazyman.class){
                if(instance==null){
                    instance=new lazyman();
                }
            }
        }
        return instance;
    }
}

        经历以上几步,懒汉思想的写法已经很好了,但还有没有和饿汉一样天生线程安全的优雅写法呢?有,它就是使用静态内部类【在静态内部类中创建静态的常量对象,有方法调用这个内部类时,静态内部类才加载,创建对象,并且只加载一次也只创建一次对象】。写法如下:

//5.使用静态内部类
public class lazyman {
    private static class node {
        private static final lazyman instance=new lazyman();
    }
    public lazyman(){

    }
    public static lazyman getInstance(){
        return node.instance;
    }
}

         以上饿汉思想与懒汉思想的写法,构成了单例模式的所有写法。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐