对于单例模式相比大家都很熟悉了,所以就不多介绍了,直接进入正题!
单例模式的特点:
- 构造器私有
- 持有自己类型的属性
- 对外提供获取实例的静态方法
单例模式的实现
我在每一种模式前都有相关介绍,这样看起来方便一些
/*
* 懒汉模式
* 延迟初始化(典型的竞态条件---先检查后执行的常见情况),这种情况下 通过一个可能失效的观测结果来查看下一步动作
* 线程不安全的
* */
class Lazy {
private Lazy() {}
private static Lazy instance;
public static Lazy getInstance() {
if (instance == null) { //多线程模式下,这个状态可能是失效的
instance = new Lazy();
}
return instance;
}
}
/*
* 饿汉模式
* 直接初始化,线程安全,但是容易产生垃圾
* */
class Hungry {
private Hungry() {}
private static Hungry instance = new Hungry();
public static Hungry getInstance() {
return instance;
}
}
/*
* 双重锁模式
* 双重检测,线程安全
* new 一个对象发生的过程:
* 1、分配空间
* 2、初始化空间
* 3、将该引用指向这个空间
* 2,3有 可能发生重排序,这样另一个线程使用的对象可能是一个半初始化的对象,不安全。
* */
class DoubleCheck {
private DoubleCheck() {}
private static volatile DoubleCheck instance;//volatile一定要有,是为了防止指令重排序
public static DoubleCheck getInstance() {
if (instance == null) {//防止不必要的实例
synchronized (DoubleCheck.class) {//只许一个线程完成初始化
if (instance == null) { //再次判断是否已经被初始化
instance = new DoubleCheck();
}
}
}
return instance;
}
}
/*
* 静态内部类单例模式
* 因为静态属性的初始化只会初始化一次,所以只有第一个线程可以初始化实例
* 线程安全,推荐
* */
class StaticInner {
private StaticInner() {}
public static StaticInner getInstance() {
return Inner.instance;
}
private static class Inner {
private static final StaticInner instance = new StaticInner();
}
}
/*
1. 枚举单例模式
2. 枚举类隐藏了私有构造器
3. 有自身类型的静态变量
4. */
enum Single_Enum {
INSTANCE;
public static Single_Enum getInstance() {
return Single_Enum.INSTANCE;
}
}
枚举单例模式是《Effective java》一书中推荐的设计模式,但是并不常用,主要是因为太简单导致可读性比较差。
常用静态内部类单例模式,简单直观,线程安全。
优缺点
优点:
1、提供了对唯一实例的受控访问。可以严格控制客户端怎么去访问它。
2、节约系统资源,尤其是那些需要频繁创建和销毁的对象
3、避免对共享资源的多重占用
缺点:
1、缺少抽象层而难以扩展
2、且单例类职责过重
3、不适用于变化的对象
单例模式的使用场景
1、Java中的Runtime类(懒汉式)
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {}
......
}
2、windows中的任务管理器、回收站、日志、应用配置文件、网站计数器,线程池,连接池等常见到的单例模式实例
上面是单例模式的一些应用举例,我们来进行一个概括
- 1、资源共享的情况下,避免资源的重复操作带来的性能消耗。如日志文件、网站计数器、配置文件对象等
- 2、当一些对象需要频繁的创建和销毁时,用单例来控制这些资源。如线程池、连接池
注:
很多博客中说,在控制资源的情况下,方便资源的相互通信,如线程池。
但是实际上线程池中的线程任务最好不要有依赖关系,否则会出现死循环的;而连接池就是一个个数据库连接,你甚至可以把它看成是一个list。所以只是用单例来去避免像线程和数据库连接这样比较昂贵对象的频繁创建和销毁,不是为了通信的。
(如果错误,请指正)
参考文章:
单例模式
单例模式的常见应用场景