设计模式-单例模式

模式意图

保证一个类只有一个实例,并提供一个全局访问点,目的就是为了节省资源,节省cpu资源,节省内存,并且自行实例化并向整个系统提供这个对象。        

场景

需要更严格的控制全局变量,避免多个对象操作使变量数据不准确。

重量级的对象如线程池对象,数据库连接池对象。

不需要多个实例对象的工具类。

特点

单例模式是创建者模式,重点关注怎样创建对象,最大的特点是将对象的创建和使用分离。

  • 单例模式只有一个实例对象
  • 单例模式对象必须由单例类自行创建
  • 单例类向外提供一个访问该单例的全局访问点

懒汉式

用的时候再把对象初始化 

public class Singleton {
    private static Singleton instance;

    private Singleton(){

    }
   
    public static Singleton getInstance2(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

优点:延迟加载,节省资源

缺点:线程不安全

 

 改进1:为了线程安全,为方法添加锁

public class Singleton {
    private static Singleton instance = new Singleton();

    //避免从外部new对象
    private Singleton(){

    }
   
    public synchronized static Singleton getInstance1(){
         if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

饿汉式只有创建对象是才有线程安全问题,一旦创建了就不存在线程安全问题了,如果每次获取对象都有锁,会造成获取对象的效率下降。

改进2:为了提高效率,将锁放到具体的判断代码上

public class Singleton {
    private static Singleton instance;

    private Singleton(){

    }
    
    public static Singleton getInstance2(){
        if(instance == null){
            synchronized(Singleton.class){
            instance = new Singleton();
            }
        }
        return instance;
    }
}
如果AB两个线程都执行完了instance == null,然后进入到了if内,A执行完创建对象后,B还是会执行创建对象,也是线程不安全的。

改进3:再次为了线程安全问题,增加了一个判断条件

public class Singleton {
    private static Singleton instance;

    private Singleton(){

    }
    
    public synchronized static Singleton getInstance2(){
        if(instance == null){
            synchronized (Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

为什么还需要在锁外面再加一个判断条件?

是为了避免每个线程进来都要加持锁从而降低效率,通过if判断效率更快。

如果AB线程同时进入第一个if条件内部,A进入执行完锁的内容,B进去后再执行一次条件,所以解决了线程安全问题。

其实还是存在问题

创建对象的操作并不是原子操作,首先要分配对象内存空间,再初始化对象,再设置变量去指向对象的内存空间。这三个步骤在JVM执行时可能会出现指令重排的问题。

 改进4:添加volatile关键字,防止指令重排

public class Singleton {
    private static volatile Singleton instance;

    private Singleton(){

    }
    
    public synchronized static Singleton getInstance2(){
        if(instance == null){
            synchronized (Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

 

饿汉式

类加载的时候就把对象初始化 

遵循着单例模式的特点创建单例模式类

public class Singleton {
    private static Singleton instance;

    //避免从外部new对象
    private Singleton(){

    }
   
    public static Singleton getInstance1(){
         if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

优点:饿汉式是线程安全的,因为在调用前就有了对象

缺点:如果单例类中还要其他的静态方法,如果大部分时候都在调用其他的静态方法,但是这个对象一直加载在内存中,就会造成资源的浪费。

静态内部类方式

public class Singleton {
    public static class Inner{
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance(){
        return Inner.INSTANCE;
    }
}

优点:天然的线程安全,静态内部类不会随着外部类的加载和初始化而初始化,它会单独的加载和初始化。如果在外部类中有一些其他的工作,相较于饿汉式的天然线程安全,这种方式更加节省资源。只有当调用getInstance方法时才会去加载内部类。

在这里内部类保证了线程安全,外部类保证了效率。

枚举方式

public class Singleton {

    private Singleton(){
    }

    public static Singleton getInstance(){
        return InnerEnum.INSTANCE.getInstance();
    }

    //枚举意思就是实例为有限个
    private static enum InnerEnum{
        /*
        因为每个枚举实例都是static final的,只会进行一次实例化
        借助枚举对象只能进行一次实例化,将需要的单例对象作为枚举类的属性,进而来实现单例模式
        * */

        INSTANCE;
        private final Singleton instance;
        private Singleton getInstance(){
            return instance;
        }
        private InnerEnum(){
            instance = new Singleton();
        }
    }
}

优点:线程最安全,枚举是不能被反射或序列化被破坏线程安全。

总结

懒汉式是时间换空间,饿汉式是空间换时间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值