码迷,mamicode.com
首页 > 其他好文 > 详细

单例模式使用

时间:2021-02-09 12:15:03      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:loading   简单   pen   构造   执行   public   私有化   不同   二次   

参考:CS-Notes 单例模式

最简单的设计模式--单例模式

枚举实现单例模式

Java setAccessible方法作用

为什么要用枚举实现单例模式(避免反射、序列化问题)

单例模式的实现方式及如何有效防止防止反射和反序列化

单例模式应用场景

1、什么是单例模式

单例模式(Singleton Pattern) :确保一个类只有一个实例,并提供一个全局访问点。

  • 应用场景:

    • 资源需要共享时,程序的日志,保证只有一个,才好追加日志内容。
    • 控制资源,比如线程池(TreadPool)创建,保证一个,方便对池中线程进行控制。
  • 单例模式的特点:

    • 构造方法私有化;
    • 实例化的变量引用私有化;
    • 获取实例的方法共有;

2、单例模式的实现方式

单元素的枚举类是实现单例模式的最佳方式 出自《effective java》

常用的单例模式:

(1)饿汉式(线程安全)

  • 静态变量
public class Singlenton{
    private final static Singleton uniqueInstance = new Singleton();
    private Singlenton(){
        
    }
    public static Singleton getUniqueInstance(){
        return uniqueInstance;
    }
}
  • 静态代码块
public class Singlenton{
    private final static Singleton uniqueInstance;
    static{
        uniqueInstance = new Singleton();
    }
    private Singlenton(){
        
    }
    public static Singleton getUniqueInstance(){
        return uniqueInstance;
    }
}

饿汉式,线程安全,利用类加载机制,静态变量和静态代码块,在类加载时就会执行,并且只执行一次,避免了多线程问题,但是丢失了延迟实例化带来的节约资源的好处。

(2)懒汉式

  • 原始版本,线程不安全:当有多个线程同时进入if判断后,会各自创建实例,此时不能保证单例。
public class Singleton{
    private static Singleton uniqueInstance;
    private Singleton(){
        
    }
    public static Singleton getInstance(){
        if(uniqueInstance==null){
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}
  • 改良版一:在getInstance类上加synchronized关键字,但是效率会大大降低,因为不管实例有无创建,线程只有调用了getInstance方法想用获取实例,都会被阻塞,等待资源。
  • 改良二:双重校验锁
public class Singleton{
    private volatile static Singleton uniqueInstance;
    private Singleton(){
        
    }
    public static Singleton getInstance(){
        if(uniqueInstance==null){
            synchronized(Singleton.class){
                if(uniqueInstance==null){
                    uniqueInstace = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

双重校验锁的好处:第一个if判断,保证了调用实例的线程都能进入方法,当实例已经创建时,可以直接返回实例对象,不需要等待。第二个if判断,是为了防止当两个线程同时进入第一个if判断里面,当线程一拿到锁,创建实例后,释放锁,线程二得到锁,因为有第二层的if判断实例是否创建,就能避免进行第二次实例化。

volatile关键字:禁止JVM指令重排,保证在多线程环境下也能正常运行。

技术图片

由于JVM具有指令重排的特性。实例在创建时需要三个步骤执行:1、为实例分配内存空间,2、初始化实例(实例可以不能null,需要初始化清理),3、将实例指向分配好的内存空间。当在单线程下,无影响,但是多线程时,当线程一执行了1和3,线程二调用getInstance方法,发现实例uniqueInstace不为null,就会把未初始化的实例返回。

什么是线程安全问题:当多线程时,有可能出现同时访问同一个资源的情况,由于每个线程执行过程不可控,所以很可能导致最终结果与实际上期望的结果不同,或者直接导致程序出错。

线程安全:概括起来,保证可见性。

(3)静态内部类

public class Singleton{
    private Singleton(){
        
    }
    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton(); 
    }
    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
    
}

当Singleton类加载时,静态内部类还没有加载,只有当调用了getInstance方法,才会调用SingletonHolder,并且只会调用一次实例化。之后调用getInstance方法,就直接返回之前的实例对象,保证单例。

(4)枚举实现

public enum EnumSingleton{
    INSTANCE;
    public EnumSingleton getInstance(){
        return INSTANCE;
    }
}

完整枚举单例:

public class User{
    //私有化构造器
    private User(){
        
    }
    //定义静态枚举类
    static enum SingletonEnum{
        //创建一个枚举对象,该对象天生为单例
        INSTANCE;
        private User user;
        //私有化构造器
        private SingletonEnum(){
            User user = new User();
        }
        
        public User getInstance(){
            return user;
        }
            
    }
    //对外暴露一个获取User对象的静态方法
    public static User getUser(){
        return SingletonEnum.INSTANCE.getInstance();
    }
}

枚举实现的优点:

  • 防止反射攻击,但是其他实现中,通过setAccessible()方法可以将私有构造器函数的访问级别设置为public,然后调用构造函数实例化对象newInstance()。但是在反射中通过newInstance()创建对象时,会检查该类是否被Enum修饰,如果是则抛出异常,反射失败。
  • 不会因为序列化而产生新实例。其他实现方式需要使用transient关键字修饰。

单例模式使用

标签:loading   简单   pen   构造   执行   public   私有化   不同   二次   

原文地址:https://www.cnblogs.com/jayzou/p/14389310.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!