单例模式使用
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关键字修饰。