单例模式

概念

  单例模式(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 中,为每个线程都提供一个对象,实际上是以
空间换时间来实现线程间隔离的。

单例模式小结

  单例模式可以保证内存里只有一个实例,减少了内存开销;可以避免对资源的多重占用。
单例模式看起来非常简单,实现起来其实也非常简单。

posted @ 2019-12-03 16:13  y个人的世界  阅读(130)  评论(0)    收藏  举报