目录与学习目标
1:什么是单例设计模式2:单例模式的应用场景3:尝试添加 synchronized 对象级别的锁 解决4:尝试添加 synchronized 类级别的锁 解决5:使用单例设计模式解决
1:什么是单例设计模式
单例设计模式(Singleton Design Pattern)理解起来非常简单。一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。
2:单例模式的应用场景
处理资源访问冲突在 UserController 和 OrderController 中,我们分别创建两个 Logger 对象。在 Web 容器的 Servlet 多线程环境下,如果两个 Servlet 线程同时分别执行 login() 和 create() 两个函数,并且同时写日志到 log.txt 文件中,那就有可能存在日志信息互相覆盖的情况。

@Slf4jpublic class Logger { private FileWriter writer; public Logger() throws IOException { File file = new File("A:\\log.txt"); //true表示追加写入 writer = new FileWriter(file, true); } public void log(String message) throws IOException { log.info("写入数据:{}",message); writer.write(message); writer.write("\n"); writer.close(); }}
public class UserController { private Logger logger = new Logger(); public UserController() throws IOException { } public void login() throws IOException { // ...省略业务逻辑代码... logger.log("HSJ User logined!"); }}
public class OrderController { private Logger logger = new Logger(); public OrderController() throws IOException { } public void create() throws IOException { // ...省略业务逻辑代码... logger.log("Created an order"); }}
3:尝试添加 synchronized 对象级别的锁 解决
这种锁是一个对象级别的锁,一个对象在不同的线程下同时调用 log() 函数,会被强制要求顺序执行。不同的对象之间并不共享同一把锁。在不同的线程下,通过不同的对象调用执行 log() 函数,锁并不会起作用,
@Slf4jpublic class Logger { private FileWriter writer; public Logger() throws IOException { File file = new File("A:\\log.txt"); //true表示追加写入 writer = new FileWriter(file, true); } public void log(String message) throws IOException { log.info("写入数据:{}",message); //添加this 为对象级别的锁 锁无效 synchronized (this) { writer.write(message); } writer.write("\n"); writer.close(); }}
4:尝试添加 synchronized 类级别的锁 解决
@Slf4jpublic class Logger { private FileWriter writer; public Logger() throws IOException { File file = new File("A:\\log.txt"); //true表示追加写入 writer = new FileWriter(file, true); } public void log(String message) throws IOException { log.info("写入数据:{}",message); //添加Logger.class 为类级别的锁 锁生效 synchronized (Logger.class) { writer.write(message); } writer.write("\n"); writer.close(); }}
5:使用单例设计模式解决
我们将 Logger 设计成一个单例类,程序中只允许创建一个 Logger 对象,所有的线程共享使用的这一个 Logger 对象,共享一个 FileWriter 对象,同时FileWriter 本身是对象级别线程安全的,也就避免了多线程情况下写日志会互相覆盖的问题。执行顺序://类加载仅执行一次//1:静态常量在类加载时初始化 调用方法new LoggerSingleton() //2:被步骤1调用 执行new LoggerSingleton() //3:在原本方法执行 new LoggerSingleton();//注意此处final修饰仅表示 LoggerSingleton instance 不可变1:private static final LoggerSingleton instance = new LoggerSingleton();2:public LoggerSingleton() {}3:public LoggerSingleton() {}//由于getInstance添加了static修饰 因此这里是直接调用getInstance方法4: public static LoggerSingleton getInstance() { return instance; }5: public static LoggerSingleton getInstance() { return instance; }
@Slf4jpublic class LoggerSingleton { //static 静态变量 在类加载时初始化 //final 变量变常量 只能被赋值一次 在类加载时初始化 private static final LoggerSingleton instance = new LoggerSingleton(); public static LoggerSingleton getInstance() { return instance; } private FileWriter writer; public LoggerSingleton() { File file = new File("A:\\log.txt"); //true表示追加写入 try { writer = new FileWriter(file, true); } catch (IOException e) { e.printStackTrace(); } } public void log(String message) throws IOException { log.info("写入数据:{}", message); writer.write(message); writer.write("\n"); writer.close(); }}public class LoggerSingletonDemoStart { public static void main(String[] args) throws IOException { //如果没有该 new LoggerSingleton(); 则上面第三步去掉 其他一致 new LoggerSingleton(); //这样就只能获取该单例的Logger对象 LoggerSingleton.getInstance().log("我是测试单例的Logger啊"); LoggerSingleton.getInstance().log("我是测试单例的Logger啊"); }}
项目连接
请配合项目代码食用效果更佳:项目地址:https://github.com/hesuijin/hesujin-design-patternGit下载地址:https://github.com.cnpmjs.org/hesuijin/hesujin-design-pattern.gitdemo-study模块 下 build_design_pattern singleton包下 loggerDemo包