单例模式是什么
单例模式要求类能够有返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法)
单例模式如何实现
单例的实现主要是通过以下三个步骤:
- 将类的构造方法定义为私有方法。这样其他类的代码就无法通过调用该类的构造方法来实例化该类的对象,只能通过该类提供的静态方法来得到该类的唯一实例。
- 定义一个私有的类的静态实例。
- 提供一个公有的获取实例的静态方法。
单例追求的目标
- 线程安全
- 懒加载
- 调用效率高
单例应用场景
- Windows系统的任务管理器
- Windows系统的回收站
- 操作系统的文件系统,一个操作系统只能有一个文件系统
- 数据库连接池的设计与实现
- 多线程的线程池设计与实现
- Spring中创建的Bean实例默认都是单例
- Java-Web中,一个Servlet类只有一个实例。
单例模式的特点
单例模式的特点:从系统启动到终止,整个过程只会产生一个实例。
单例模式常用写法有什么
懒汉式: 线程安全的懒汉模式,在第一次使用的时候才进行初始化,达到了懒加载的效果;由于获取实例的静态方法用synchronized锁修饰,所以线程安全;但是,这种方法每次获取实例都要进行同步(加锁),因此效率较低,并且可能很多同步都是没必要的。
优点:线程安全、懒加载
缺点:效率低
是否推荐:可以使用,但不推荐。
注:该模式还有另一种常见写法,就是把getInstance方法上的synchronized去掉,这种方法有线程安全问题,不能使用。最后会讲到为什么有线程安全问题
饿汉式:饿汉模式,在类加载的时候就对实例进行初始化,没有线程安全问题;获取实例的静态方法没有使用同步,调用效率高;但是没有使用懒加载,如果该实例从始至终都没被使用过,则会造成内存浪费。
优点:线程安全、效率高
缺点:非懒加载
是否推荐:可以使用,但不推荐。
双重校验
对于这段程序你们可能遇到的几个问题我会一一进行解答
- 为什么要设置private?
不能对其随意的进行读写操作,如果都可以对其操作,程序就不是安全的了(场景一定是多线程下的)
- 为什么是volatile?
加 volatile 是为了禁止指令重排序,即避免某个线程获取到其他线程没有初始化的对象。
- 为什么是static?
保证单例对象是属于类的,而不是属于对象实例的,保证一个类只有一个变量。
- 为什么构造方法是私有的?
为了防止外部类通过构造器创建新的对象,保证只能有一个对象,防止线程通过new的方式创建对象
方法为什么是静态的方法?因为如果属性是静态的,而方法不是,方法想要访问属性就必须new才能访问,如果属性不是静态的,而方法是,那么方法无法访问到静态属性。
多线程下,instance如果不是私有的,会被new产生新对象
之所以这样设计,是因为类的静态内部类在JVM中是唯一的,这很好地保障了单例对象的唯一性。
-
为什么会有两个if判空?
-第一个if减少性能开销,提高效率,避免 INSTANCE 不为null时仍然去竞争锁。第二个if避免多线程重复创建对象。 -
第一个if是干嘛的?————是为了代码提高代码执行效率
由于单例模式只要一次创建实例即可,所以当创建了一个实例之后,再次调用就不必要进入同步代码块,不用竞争锁。如果已经创建了实例的话,直接返回前面创建的实例即可。 -
第二个if是干嘛的?————是为了拦截其他线程
如果没有第二次校验,假设线程t1执行了第一次校验后,判断为null,这时t2也获取了CPU执行权,也执行了第一次校验,判断也为null。接下来t2获得锁,创建实例。这时t1又获得CPU执行权,由于之前已经进行了第一次校验,结果为null(不会再次判断),获得锁后,直接创建实例。结果就会导致创建多个实例。所以需要在同步代码里面进行第二次校验,如果实例为空,则进行创建。当有多个同时进入第一个判断,有锁的话可以在第一个获取锁的线程执行完之后,第二个就不会再去创建实例。