37、单例模式(上)

1、单例

单例设计模式(Singleton Design Pattern)理解起来非常简单
一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式

2、处理资源访问冲突

2.1、示例

自定义实现了一个往文件中打印日志的 Logger 类,具体的代码实现如下所示

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(message);
    }
}
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());
    }
}

2.2、问题

在上面的代码中我们注意到,所有的日志都写入到同一个文件 /Users/wangzheng/log.txt 中
在 UserController 和 OrderController 中,我们分别创建两个 Logger 对象,在 Web 容器的 Servlet 多线程环境下
如果两个 Servlet 线程同时分别执行 login() 和 create() 两个函数,并且同时写日志到 log.txt 文件中,那就有可能存在日志信息互相覆盖的情况
image

2.3、解决 1

那如何来解决这个问题呢,我们最先想到的就是通过加锁的方式
给 log() 函数加互斥锁(Java 中可以通过 synchronized 的关键字),同一时刻只允许一个线程调用执行 log() 函数

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(message);
        }
    }
}

这真的能解决多线程写入日志时互相覆盖的问题吗:答案是否定的
因为这种锁是一个对象级别的锁,一个对象在不同的线程下同时调用 log() 函数,会被强制要求顺序执行,但是不同的对象之间并不共享同一把锁
在不同的线程下,通过不同的对象调用执行 log() 函数,锁并不会起作用,仍然有可能存在写入日志互相覆盖的问题
image
image

2.4、解决 2

我这里稍微补充一下,在刚刚的讲解和给出的代码中,我故意 "隐瞒" 了一个事实:我们给 log() 函数加不加对象级别的锁,其实都没有关系
因为 FileWriter 本身就是线程安全的,它的内部实现中本身就加了对象级别的锁,因此在外层调用 write() 函数的时候,再加对象级别的锁实际上是多此一举
因为不同的 Logger 对象不共享 FileWriter 对象,所以 FileWriter 对象级别的锁也解决不了数据写入互相覆盖的问题

那我们该怎么解决这个问题呢:我们只需要把对象级别的锁,换成类级别的锁就可以了
让所有的对象都共享同一把锁,这样就避免了不同对象之间同时调用 log() 函数,而导致的日志覆盖问题

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(message);
        }
    }
}

2.5、解决 3

我们将 Logger 设计成一个单例类,程序中只允许创建一个 Logger 对象
所有的线程共享使用的这一个 Logger 对象,共享一个 FileWriter 对象,而 FileWriter 本身是对象级别线程安全的,也就避免了多线程情况下写日志会互相覆盖的问题

public class Logger {

    private FileWriter writer;

    private Logger() {
        File file = new File("/Users/wangzheng/log.txt");
        writer = new FileWriter(file, true); // true 表示追加写入
    }

    public void log(String message) {
        writer.write(message);
    }

    private static final Logger instance = new Logger();

    public static Logger getInstance() {
        return instance;
    }
}
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());
    }
}

3、表示全局唯一类

从业务概念上,如果有些数据在系统中只应保存一份,那就比较适合设计为单例类

  • 配置信息类:在系统中,我们只有一个配置文件,当配置文件被加载到内存之后,以对象的形式存在,也理所应当只有一份
  • 唯一递增 ID 号码生成器,如果程序中有两个对象,那就会存在生成重复 ID 的情况,所以我们应该将 ID 生成器类设计为单例
public class IdGenerator {

    // AtomicLong 是一个 Java 并发库中提供的一个原子变量类型
    // 它将一些线程不安全需要加锁的复合操作封装为了线程安全的原子操作
    // 比如下面会用到的 incrementAndGet()
    private AtomicLong id = new AtomicLong(0);

    private IdGenerator() {
    }

    public long getId() {
        return id.incrementAndGet();
    }

    private static final IdGenerator instance = new IdGenerator();

    public static IdGenerator getInstance() {
        return instance;
    }
}

// IdGenerator 使用举例
long id = IdGenerator.getInstance().getId();

4、如何实现一个单例

4.1、饿汉式

public class IdGenerator {

    private IdGenerator() {
    }

    private static final IdGenerator INSTANCE = new IdGenerator();

    public static IdGenerator getInstance() {
        return INSTANCE;
    }

    private final AtomicLong id = new AtomicLong(0);

    public long getId() {
        return id.incrementAndGet();
    }
}

4.2、懒汉式

public class IdGenerator {

    private IdGenerator() {
    }

    private static IdGenerator instance;

    public static synchronized IdGenerator getInstance() {
        if (instance == null) {
            instance = new IdGenerator();
        }
        return instance;
    }

    private final AtomicLong id = new AtomicLong(0);

    public long getId() {
        return id.incrementAndGet();
    }
}

4.3、双重检查

public class IdGenerator {

    private IdGenerator() {
    }

    private static volatile IdGenerator instance;

    public static IdGenerator getInstance() {
        if (instance == null) {
            synchronized (IdGenerator.class) {
                if (instance == null) {
                    instance = new IdGenerator();
                }
            }
        }
        return instance;
    }

    private final AtomicLong id = new AtomicLong(0);

    public long getId() {
        return id.incrementAndGet();
    }
}

4.4、静态内部类

public class IdGenerator {

    private IdGenerator() {
    }

    public static IdGenerator getInstance() {
        return InstanceHolder.INSTANCE;
    }

    private static class InstanceHolder {
        private static final IdGenerator INSTANCE = new IdGenerator();
    }

    private final AtomicLong id = new AtomicLong(0);

    public long getId() {
        return id.incrementAndGet();
    }
}

4.5、枚举

public enum IdGenerator {

    INSTANCE;

    private final AtomicLong id = new AtomicLong(0);

    public long getId() {
        return id.incrementAndGet();
    }
}
posted @ 2023-06-26 15:19  lidongdongdong~  阅读(18)  评论(0编辑  收藏  举报