单例模式
概念
单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并
提供一个全局访问点。单例模式是创建型模式。
饿汉式单例
饿汉式单例是在类加载的时候就立即初始化,并且创建单例对象。绝对线程安全,在线
程还没出现以前就是实例化了,不可能存在访问安全问题。
优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。
缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存,有可能占着茅
坑不拉屎。
样例:
(1)静态属性
/**
* 优点:执行效率高,性能高,没有任何的锁
* 缺点:某些情况下,可能会造成内存浪费
*/
public class HungrySingleton {
private static final HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
(2)静态块
public class HungryStaticSingleton {
private static final HungryStaticSingleton hungrySingleton;
static {
hungrySingleton = new HungryStaticSingleton();
}
private HungryStaticSingleton(){}
public static HungryStaticSingleton getInstance(){
return hungrySingleton;
}
}
懒汉式单例
懒汉式单例的特点是:被外部类调用的时候内部类才会加载,但存在线程不安全;
由于线程不安全需要加synchronized,线程安全的问题便解决了。但是,用
synchronized 加锁,在线程数量比较多情况下,如果 CPU 分配压力上升,会导致大批
量线程出现阻塞,从而导致程序运行性能大幅下降。
这时可以采用双重检查锁和内部类(内部类一定是要在方法调用之前初始化)
样例:
(1)简单懒汉式
/**
* 优点:节省了内存,线程安全
* 缺点:性能低
*/
public class LazySimpleSingletion {
private static LazySimpleSingletion instance;
private LazySimpleSingletion(){}
public synchronized static LazySimpleSingletion getInstance(){
if(instance == null){
instance = new LazySimpleSingletion();
}
return instance;
}
}
(2)双重检查锁
/**
* 优点:性能高了,线程安全了
* 缺点:可读性难度加大,不够优雅
*/
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton instance;
private LazyDoubleCheckSingleton(){}
public static LazyDoubleCheckSingleton getInstance(){
//检查是否要阻塞
if (instance == null) {
synchronized (LazyDoubleCheckSingleton.class) {
//检查是否要重新创建实例
if (instance == null) {
instance = new LazyDoubleCheckSingleton();
//指令重排序的问题
}
}
}
return instance;
}
}
(3)内部类
/**
ClassPath : LazyStaticInnerClassSingleton.class
LazyStaticInnerClassSingleton$LazyHolder.class
优点:写法优雅,利用了Java本身语法特点,性能高,避免了内存浪费,不能被反射破坏
缺点:不优雅
*/
public class LazyStaticInnerClassSingleton {
private LazyStaticInnerClassSingleton(){
if(LazyHolder.INSTANCE != null){
throw new RuntimeException("不允许非法访问");
}
}
private static LazyStaticInnerClassSingleton getInstance(){
return LazyHolder.INSTANCE;
}
private static class LazyHolder{
private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton();
}
}
反射破坏单例
通过反射来调用其构造方法,然后,再调用 getInstance()方法
解决方法:在构造方法加限制(判断实例是否加载)
序列化破坏单例
当我们将一个单例对象创建好,有时候需要将对象序列化然后写入到磁盘,下次使用时
再从磁盘中读取到对象,反序列化转化为内存对象。反序列化后的对象会重新分配内存,
即重新创建。
解决方法:增加readResolve()(jvm底层)解决了单例被破坏的问题。但是实际上实例化了两次,只不过新创建的对象没有被返回而已
注册式单例
注册式单例又称为登记式单例,就是将每一个实例都登记到某一个地方,使用唯一的标
识获取实例。注册式单例有两种写法:一种为容器缓存,一种为枚举登记。
ThreadLocal 线程单例
ThreadLocal 不能保证其创建的对象是全局唯一,但是能保证在单个线程中是唯一的,天生的线程安全。
ThreadLocal 实现原理:上面的单例模式为了达到线程安全的目的,给方法上锁,以时间换空间。ThreadLocal
将所有的对象全部放在 ThreadLocalMap 中,为每个线程都提供一个对象,实际上是以
空间换时间来实现线程间隔离的。
单例模式小结
单例模式可以保证内存里只有一个实例,减少了内存开销;可以避免对资源的多重占用。
单例模式看起来非常简单,实现起来其实也非常简单。
浙公网安备 33010602011771号