单例设计模式(Singleton Design Pattern)理解起来非常简单。一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。
案例1:处理资源访问冲突
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class Logger { private FileWriter writer; public Logger() { File file = new File("/Users/wangzheng/log.txt"); writer = new FileWriter(file, 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()); } }
|
通过上面的例子可以看出,我们在两个controller中创建了两个Logger对象,在多线程环境下,如果两个 Servlet 线程同时分别执行 login() 和 create() 两个函数,同时写入日志文件,则可能会导致日志互相覆盖的情况。
解决办法:
1、加锁
我们可以给Logger对象加一把类级别的锁,同一时刻只有一个线程可以访问Logger对象。
1 2 3 4 5 6 7 8 9 10 11 12
| public class Logger { private FileWriter writer; public Logger() { File file = new File("/Users/wangzheng/log.txt"); writer = new FileWriter(file, true); } public void log(String message) { synchronized(Logger.class) { writer.write(mesasge); } } }
|
另外,分布式锁以及并发队列也可以解决这个问题。
2、单例模式
我们将 Logger 设计成一个单例类,程序中只允许创建一个 Logger 对象,所有的线程共享 使用的这一个 Logger 对象,共享一个 FileWriter 对象,而 FileWriter 本身是对象级别线 程安全的,也就避免了多线程情况下写日志会互相覆盖的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public class Logger { private FileWriter writer; private static final Logger instance = new Logger(); private Logger() { File file = new File("/Users/wangzheng/log.txt"); writer = new FileWriter(file, true); } public static Logger getInstance() { return instance; } public void log(String message) { writer.write(mesasge); } }
public class UserController { public void login(String username, String password) { Logger.getInstance().log(username + " logined!"); } } public class OrderController { private Logger logger = new Logger(); public void create(OrderVo order) { Logger.getInstance().log("Created a order: " + order.toString()); } }
|
案例2:表示全局唯一类
从业务概念上,如果有些数据在系统中只应保存一份,那就比较适合设计为单例类,比如配置信息类,全局唯一id的生成器类等。
此处省略代码。
如何实现一个单例?
实现单例需要关注以下几个点:
- 构造函数需要是 private 访问权限的,这样才能避免外部通过 new 创建实例;
- 考虑对象创建时的线程安全问题;
- 考虑是否支持延迟加载;
- 考虑 getInstance() 性能是否高(是否加锁)。
1. 饿汉式
饿汉式的实现方式比较简单。在类加载的时候,instance 静态实例就已经创建并初始化好 了,所以,instance 实例的创建过程是线程安全的。不过,这样的实现方式不支持延迟加 载(在真正用到 IdGenerator 的时候,再创建实例)。
1 2 3 4 5 6 7 8 9 10 11
| public class IdGenerator { private AtomicLong id = new AtomicLong(0); private static final IdGenerator instance = new IdGenerator(); private IdGenerator() {} public static IdGenerator getInstance() { return instance; } public long getId() { return id.incrementAndGet(); } }
|
2. 懒汉式
懒汉式相对于饿汉式的优势是支持延迟加载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class IdGenerator { private AtomicLong id = new AtomicLong(0); private static IdGenerator instance; private IdGenerator() {} public static synchronized IdGenerator getInstance() { if (instance == null) { instance = new IdGenerator(); } return instance; } public long getId() { return id.incrementAndGet(); } }
|
缺点也很明显,我们加了锁,导致并发度之有1,即串行操作,如果频繁的被用到,则会影响性能。
3. 双重检测
该方法既支持延迟 加载、又支持高并发的单例实现方式。该方式中,只要 instance 被创建之后,即便再调用 getInstance() 函数也不会再 进入到加锁逻辑中了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class IdGenerator { private AtomicLong id = new AtomicLong(0); private static IdGenerator instance; private IdGenerator() {} public static IdGenerator getInstance() { if (instance == null) { synchronized(IdGenerator.class) { if (instance == null) { instance = new IdGenerator(); } } } return instance; } public long getId() { return id.incrementAndGet(); } }
|
这种实现在低版本的jdk中,会因指令重排序,可能会导致 IdGenerator 对象被 new 出来,并且赋值给 instance 之后,还没来得及初始化(执行构造函数中的代码逻辑),就被另一个线程使用了。我们需要给 instance 成员变量加上 volatile 关键字,禁止指令重排序才 行。
但是在高版本的jdk不存在这个问题。
4. 静态内部类
它有点 类似饿汉式,但又能做到了延迟加载。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class IdGenerator { private AtomicLong id = new AtomicLong(0); private IdGenerator() {} private static class SingletonHolder{ private static final IdGenerator instance = new IdGenerator(); } public static IdGenerator getInstance() { return SingletonHolder.instance; } public long getId() { return id.incrementAndGet(); } }
|
SingletonHolder 是一个静态内部类,当外部类 IdGenerator 被加载的时候,并不会创建 SingletonHolder 实例对象。只有当调用 getInstance() 方法时,SingletonHolder 才会 被加载,这个时候才会创建 instance。insance 的唯一性、创建过程的线程安全性,都由 JVM 来保证。
参考
《设计模式之美》