单例模式

单例模式,是一种常用的软件设计模式,在它的核心结构中只包含一个被称为单例的特殊类,通过单例模式可以保证系统中一个类只有一个实例。即一个类只有一个对象实例。

第一种:饿汉式

public class SingleEasy {
    private SingleEasy(){

    }
    //类初始化的时候就已经加载
    private static SingleEasy singleEasy=new SingleEasy();
    public static SingleEasy getInstance(){
        return singleEasy;
    }
}

饿汉式的话不存在多线程同步加载的问题,类初始化时就已经加载,做不到使用的时候才加载。

第二种:懒汉式(线程不安全)

public class SingleNotEmptyEasy {
    private static SingleNotEmptyEasy singleNotEmptyEasy;
    private SingleNotEmptyEasy(){

    }
    public static SingleNotEmptyEasy getInstance(){
        //对象为空才去实例化
        if(singleNotEmptyEasy==null){
            singleNotEmptyEasy=new SingleNotEmptyEasy();
        }
        return singleNotEmptyEasy;
    }
}

懒汉式是在使用的时候才会去加载,不过当多次同时去加载的时候就会存在线程安全问题。

第三种:懒汉式(线程安全)

public class SingleNotEmptyEasy {
    private static SingleNotEmptyEasy singleNotEmptyEasy;
    private SingleNotEmptyEasy(){

    }
    public static synchronized  SingleNotEmptyEasy getInstance(){
        //对象对空才去实例化
        if(singleNotEmptyEasy==null){
            singleNotEmptyEasy=new SingleNotEmptyEasy();
        }
        return singleNotEmptyEasy;
    }
}

就是在第二种的getInstance方法上面加了一个同步锁synchronized,每次在使用加载的时候都会先去校验,如果已经有在加载了,就不会去加载,而是要等前面的加载完了,才会去加载,这样线程安全,但是每次都去校验synchronized会造成效率的下降。

第四种:DCL

public class DCLSingle {
    private volatile static DCLSingle dclSingle;
    private DCLSingle(){

    }
    public static DCLSingle getInstance(){
        if(dclSingle==null){
            synchronized (DCLSingle.class){
                if(dclSingle==null){
                    dclSingle=new DCLSingle();
                }
            }
        }
        return dclSingle;
    }
}

DCL模式的不同是没有将同步锁加载方法上,而是加在代码块里面,如果单例对象不为空就直接返回,如果为空就会走到加了同步锁的代码块中,还会进行第二次校验,这样子提高了效率,同时也保证了线程的安全,这种模式还涉及到一个java的关键字volatile,加了volatile,可以保证无论何时读取这个变量,都是读到内存中最新的值,无论何时写这个变量,都可以立即写到内存中。
volatile关键字:
1.防止重排序
2.线程的可见性

先说防止重排序,在实例化一个对象的时会走下面三步:
1.开辟一块空间(内存)
2.初始化对象
3.给变量赋值(指向内存地址)
但是java的多线程会导致第二步和第三步顺序的不确定性,有可能会出现下面现象;
1.开辟一块空间(内存)
2.给变量赋值(指向内存地址)
3.初始化对象
在这里插入图片描述
第二步和第三步重排后,这个时候去调用对象时,就会出现对象为空的问题,加上volatile关键字刚好可以解决重排序的问题;

线程的可见性:某一个线程改了公用对象(变量),短时间内另一个线程可能是不可见的,因为每一个线程都有自己的缓存区(线程工作区)

public class NoVisibility {
    private static boolean ready;
    private static int number;
    
    private static class ReaderThread extends Thread {
        public void run() {
            while(!ready) {
                Thread.yield();
            }
            System.out.println(number);
        }
    }
    public static void main(String[] args) {
        new ReaderThread().start();
        number = 100;
        ready = true;
    }
}

在main方法中开启一个线程,接着给number赋值,改变ready为true,按道理会跳出while循环,但是运行的结果是while在一直循环,因为这里没有用到同步机制,main进程写的ready和number值,不能保证ReaderThread看到,加上volatile关键后,就会避免这种现象的出现;
第五种:内部类模式

public class InnnerClassSingle {
    private InnnerClassSingle(){

    }
    private static class SingleHolder{
        private static InnnerClassSingle innnerClassSingle=new InnnerClassSingle();
    }
    public static InnnerClassSingle getInstance(){
        return SingleHolder.innnerClassSingle;
    }
}

内部类模式是一种比较优雅的饿汉式,它是在InnnerClassSingle内中再建一个SingleHolder内部类,在内部类中创建一个InnnerClassSingle对象,通过内部类调用加载实例化,这样子不会随着类的加载的而实例化,做到了使用的时候才实例化。

第六种:枚举模式

public enum  EnumManager {
    SDCardManager(10){
        @Override
        public EnumManager getSingle() {
            return SDCardManager;
        }
    }
    ,
    HttpManager(1){
        @Override
        public EnumManager getSingle() {
            return HttpManager;
        }
    };
    
    public abstract EnumManager getSingle();
    private EnumManager(int type){

    }
}

这里需要注意的是枚举的构造方法是私有的,变量的声明要在构造方法之前。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值