为什么要使用单例?
一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例 模式。
能解决两种问题:
实战案例一:处理资源访问冲突
public class Logger {
private FileWriter writer;
public Logger() {
File file = new File("/Users/wangzheng/log.txt");
writer = new FileWriter(file, true); //true表示追加写入
}
public void log(String message) {
writer.write(mesasge);
}
}
// Logger类的应用示例:
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());
}
}
如果两个 Servlet 线程同时 分别执行 login() 和 create() 两个函数,并且同时写日志到 log.txt 文件中,那就有可能存 在日志信息互相覆盖的情况。
原因:log.txt 文件是竞争资源,两个线程同时往里面写数据
解决方法:加锁,并且要把对象级别的锁,换成类级别的锁。
public class Logger {
private FileWriter writer;
public Logger() {
File file = new File("/Users/wangzheng/log.txt");
writer = new FileWriter(file, true); //true表示追加写入
}
public void log(String message) {
synchronized(this)
{ writer.write(mesasge); }
}
}
public class Logger {
private FileWriter writer;
public Logger() {
File file = new File("/Users/wangzheng/log.txt");
writer = new FileWriter(file, true); //true表示追加写入
}
public void log(String message) {
synchronized(Logger.class) { // 类级别的锁
writer.write(mesasge); }
}
}
两外两种解决方法:
- 并发队列
- 单例模式
单例模式:
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); //true表示追加写入 }
public static Logger getInstance() {
return instance; }
public void log(String message) {
writer.write(mesasge);
}
}
// Logger类的应用示例:
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()); }
}
实战案例二:表示全局唯一类
如果有些数据在系统中只应保存一份,那就比较适合设计为单例类。
import java.util.concurrent.atomic.AtomicLong;
public class IdGenerator {
// AtomicLong是一个Java并发库中提供的一个原子变量类型,
// 它将一些线程不安全需要加锁的复合操作封装为了线程安全的原子操作,
// 比如下面会用到的incrementAndGet().
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(); }}
// IdGenerator使用举例 long id = IdGenerator.getInstance().getId();
如何实现一个单例?
- 构造函数需要是 private 访问权限的,这样才能避免外部通过 new 创建实例;
- 考虑对象创建时的线程安全问题;
- 考虑是否支持延迟加载;
- 考虑 getInstance() 性能是否高(是否加锁)。
饿汉式
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(); } }
采用饿汉式实现方式,将耗时的初始化操作, 提前到程序启动的时候完成,这样就能避免在程序运行的时候,再去初始化导致的性能问题。
懒汉式
懒汉式相对于饿汉式的优势是支持延迟加载。
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();
}
}
如果频繁地用到,那频繁加锁、释放锁及并发度低等问题,会导致性能瓶颈,这种实现方式就不可取了。
双重检测
饿汉式不支持延迟加载,懒汉式有性能问题,不支持高并发。既支持延迟 加载、又支持高并发的单例实现方式,也就是双重检测实现方式。
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();
}
}
在这种实现方式中,只要 instance 被创建之后,即便再调用 getInstance() 函数也不会再 进入到加锁逻辑中了。
静态内部类
比双重检测更加简单的实现方法
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 来保证。所以,这种实现方法既保证了线程安全,又能做到延迟加载。
枚举
public enum IdGenerator {
INSTANCE; private AtomicLong id = new AtomicLong(0);
public long getId() {
return id.incrementAndGet();
}
}