手写七种单例模式

Java中单例模式定义:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。”

单例模式应用的场景一般发现在以下条件下:

(1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。

(2)控制资源的情况下,方便资源之间的互相通信。如线程池等。

第一种  饿汉模式

定义:在类加载的时候就立即初始化,并且创建单例对象。绝对线程安全,在线程还没出现以前就是实例化了,不可能存在访问安全问题。


优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。


缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存。


Spring 中 IOC 容器 ApplicationContext 本身就是典型的饿汉式单例。

public class HungrySingleton {
    public static HungrySingleton hungrySingleton = null;
    static {
        hungrySingleton = new HungrySingleton();
    }
    private HungrySingleton() {
    }
    public HungrySingleton getInstance() {
        return hungrySingleton;
    }
}

第二种 懒汉式单例

懒汉式单例的特点是:被外部类调用的时候内部类才会加载。

public class LazySingleton {
    public static LazySingleton lazySingleton = null;
    private LazySingleton() {
    }
    //synchronized 解决多线程并发安全问题
    public synchronized static LazySingleton getInstance() {
        if (lazySingleton == null) {
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

第三种 双重检查锁单例

线程安全的问题虽然解决了。但是,用synchronized 加锁,在线程数量比较多情况下,如果 CPU 分配压力上升,会导致大批量线程出现阻塞,从而导致程序运行性能大幅下降。

那么,有没有一种更好的方式,既兼顾线程安全又提升程序性能呢?答案是肯定的。我们来看双重检查锁的单例模式:

public class DoubleLockCheckSingleton {
    // volatile可以实现多线程的可见性 有序性 但是不保证一致性
    public volatile static DoubleLockCheckSingleton doubleLockCheckSingleton = null;
    private DoubleLockCheckSingleton() {}
    public static DoubleLockCheckSingleton getInstance() {
        // 最外成if 过滤掉不为null 的线程 提高效率
        if (doubleLockCheckSingleton == null) {
            synchronized (DoubleLockCheckSingleton.class) {
                if (doubleLockCheckSingleton == null) {
                    doubleLockCheckSingleton = new DoubleLockCheckSingleton();
                }
            }
        }
        return doubleLockCheckSingleton;
    }
}

第四种 内部类单例

虽然双重检查锁确实优化了很多,但是,用到 synchronized 关键字,总归是要上锁,对程序性能还是存在一定影响的。难道就真的没有更好的方案吗?

当然是有的。我们可以从类初始化角度来考虑,

内部类一定是要在方法调用之前初始化,巧妙地避免了线程安全问题。 

/**
 * 内部类 线程安全 懒汉式
 * 完美的解决类懒汉时的内存浪费和synchronized的性能问题
 */
public class InnerClassSingleton {
    private InnerClassSingleton () {
        // 防止被反射破坏
        if (InnerClassHolder.innerClassSingleton != null) {
            throw new RuntimeException("不允许创建多实例");
        }
    }
    // static为了使用单利内存共性 final为了让方法不被重写 重载
    public static final InnerClassSingleton getInstance() {
        return InnerClassHolder.innerClassSingleton;
    }
    // 默认不加载 使用的时候才会加载
    private static class InnerClassHolder {
        private static final InnerClassSingleton innerClassSingleton = new InnerClassSingleton();
    }
}

第五种 注册式单例之枚举式单例

注册式单例又称为登记式单例,就是将每一个实例都登记到某一个地方,使用唯一的标识获取实例。

注册式单例有两种写法:一种为容器缓存,一种为枚举登记。先来看枚举式单例的写法,来看代码:

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

第六种 注册式单例之容器缓存

容器式写法适用于创建实例非常多的情况,便于管理。

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ContainerSingleton {
    private ContainerSingleton() {}
    public static Map<String, Object> ioc = new ConcurrentHashMap<>();
    public static Object getInstance(String className) {
        synchronized (ioc) {
            Object obj = null;
            if (!ioc.containsKey(className)) {
                try {
                    obj = Class.forName(className).newInstance();
                    ioc.put(className, obj);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return obj;
            } else {
                return ioc.get(className);
            }
        }
    }
}

第七种  ThreadLocal 线程单例 (注意是线程单例)

ThreadLocal 不能保证其创建的对象是全局唯一,但是能保证在单个线程中是唯一的,天生的线程安全。下面我们来看代码:

那么 ThreadLocal 是如果实现单例呢?我们知道上面的单例模式为了达到线程安全的目的,给方法上锁,以时间换空间。

ThreadLocal将所有的对象全部放在 ThreadLocalMap 中,为每个线程都提供一个对象,实际上是以空间换时间来实现线程间隔离的 。

/**
 * 每个线程内 独立单例
 */
public class ThreadLocalSingleton {
    private ThreadLocalSingleton() {}
    public static final ThreadLocal<ThreadLocalSingleton> threadLocalSingleton =
            ThreadLocal.withInitial(() -> new ThreadLocalSingleton());
    public static ThreadLocalSingleton getInstance() {
        return threadLocalSingleton.get();
    }
}

单例模式总结

单例模式可以保证内存里只有一个实例,减少了内存开销;可以避免对资源的多重占用。

posted @ 2020-05-24 00:29  南鸽  阅读(1476)  评论(0编辑  收藏  举报