单例设计模式了解起来非常简单。一个类只容许创立一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫单例模式。
应用场景
解决资源拜访抵触
上面的示例中如果每个类都创立一个 Logger 实例,就可能造成日志内容被笼罩的状况。
public class Logger { private FileWriter writer; public Logger() { File file = new File("log.txt"); writer = new FileWriter(file, true); //true示意追加写入 } public void log(String message) { writer.write(mesasge); }}public class UserController { private Logger logger = new Logger(); public void login(String username, String password) { // ...省略业务逻辑代码... logger.log(username + " logined!"); }}public class OrderController { private Logger logger = new Logger(); public void create(OrderVo order) { // ...省略业务逻辑代码... logger.log("Created an order: " + order.toString()); }}
示意全局惟一类
如果有些数据在零碎中只应保留一份,那就比拟适宜设计为单例类。比方,配置信息类,全局 ID 生成器等。
如何实现一个单例?
要实现一个单例,咱们要思考以下几点:
- 构造函数须要是 private 拜访权限的,这样能力防止内部通过 new 创立实例;
- 思考对象创立时的线程平安问题;
- 思考是否反对提早加载;
- 思考 getInstance() 性能是否高(是否加锁)。
饿汉式
public class Singleton { private static final Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; }}
懒汉式
懒汉式绝对于饿汉式的劣势是反对提早加载。但毛病也很显著,因为应用了synchronized
关键字导致这个办法的并发度很低。如果这个单例类偶然会被用到,那这种实现形式还能够承受。然而,如果频繁地用到,就会导致性能瓶颈,这种实现形式就不可取了。
public class Singleton { private static Singleton instance; private Singleton() {} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; }}
双重检测
这是一种既反对提早加载、又反对高并发的单例实现形式。
public class Singleton { private static Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { // 此处为类级别的锁 if (instance == null) { instance = new Singleton(); } } } return instance; }}
在 java1.5 以下instance = new Singleton();
有指令重排问题,须要给instance
成员变量加上volatile
关键字,java1.5 之后不会再这个有问题。
动态外部类
这种形式利用了 Java 的动态外部类,有点相似饿汉式,但又能做到了提早加载。
当外部类 Singleton 被加载的时候,并不会创立 SingletonHolder 实例对象。只有当调用 getInstance() 办法时,SingletonHolder 才会被加载,这个时候才会创立 instance。insance 的唯一性、创立过程的线程安全性,都由 JVM 来保障。所以,这种实现办法既保证了线程平安,又能做到提早加载。
public class Singleton { private Singleton() {} private static class SingletonHolder{ private static final Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.instance; }}
枚举
这是一种最简略的实现形式,基于枚举类型的单例实现。这种实现形式通过 Java 枚举类型自身的个性,保障了实例创立的线程安全性和实例的唯一性。
public enum IdGenerator { INSTANCE; private AtomicLong id = new AtomicLong(0); public long getId() { return id.incrementAndGet(); }}
如何实现线程惟一的单例?
下面的单例类对象是过程惟一的,一个过程只能有一个单例对象。那如何实现一个线程惟一的单例呢?
假如 IdGenerator 是一个线程惟一的单例类。在线程 A 内,咱们能够创立一个单例对象 a。因为线程内惟一,在线程 A 内就不能再创立新的 IdGenerator 对象了,而线程间能够不惟一,所以,在另外一个线程 B 内,咱们还能够从新创立一个新的单例对象 b。
咱们通过一个 ConcurrentHashMap 来存储对象,其中 key 是线程 ID,value 是对象。这样咱们就能够做到,不同的线程对应不同的对象,同一个线程只能对应一个对象。实际上,Java 语言自身提供了 ThreadLocal 工具类,能够更加轻松地实现线程惟一单例。
public class IdGenerator { private AtomicLong id = new AtomicLong(0); private static final ConcurrentHashMap<Long, IdGenerator> instances = new ConcurrentHashMap<>(); private IdGenerator() {} public static IdGenerator getInstance() { Long currentThreadId = Thread.currentThread().getId(); instances.putIfAbsent(currentThreadId, new IdGenerator()); return instances.get(currentThreadId); } public long getId() { return id.incrementAndGet(); }}