之前浅显的了解单例模式,只知其果不知其因,被面试官疯狂教做人,决定重写这篇文章,之前写的实在是简单了,寄希望于下次面试能够在设计模式秀一波。
首先单例模式是如何实现单例的,这是十分重要的,那就是构造函数私有化,这个操作十分巧妙,使得外部无法创建该对象,保证了单例的这个“单”字,即只有一个实例。其他对象只能通过单例对象的公共方法获得单例对象的实例。
public class Main {
private Main()
{
System.out.println("Main 构造函数");
}
public static void main(String[] args) {
Main main=new Main();
}
}
运行结果:
Main 构造函数
public class test3 {
public static void main(String[]args)
{
Main main=new Main();//报错
}
}
在了解了单例模式的含义后,我们再来看单例模式的几种写法。
单例模式
应用场景:有些对象只需要一个就足够了
作用:整个应用程序的某个实例有且只有一个
类型:饿汉模式、懒汉模式、线程安全的懒汉模式、双锁校验模式
饿汉模式
特点:
- .加载类比较慢,运行时获取对象速度比较快
- 线程安全
public class Singleton{
//1.构造方法私有化,不许外部直接创建对象
private Singleton()
{
}
//2.创建类的唯一实例
static private Singleton instance=new Singleton();
//3.提供获取实例方法
public static Singleton getInstance()
{
return instance;
}
}
懒汉模式
特点:
- .加载类比较快,运行时获取对象速度比较慢
- 线程不安全
public class Singleton{
//1.构造方法私有化,不允许外部直接创建对象
private Singleton()
{
}
//2.声明类的唯一实例
static private Singleton instance;
//3.提供获取实例的方法
public static Singleton getInstance()
{
if(instance==null)
{
instance=new Singleton();
}
return instance;
}
}
线程安全的懒汉模式
- 线程安全,但是由于锁住了整个方法,速度相对来说是很慢的
public class Singleton{
//1.构造方法私有化,不允许外部直接创建对象
private Singleton()
{
}
//2.声明类的唯一实例
static private Singleton instance;
//3.提供获取实例的方法
public static synchronized Singleton getInstance()
{
if(instance==null)
{
instance=new Singleton();
}
return instance;
}
}
双锁校验
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
我们需要关注的重点有两个:
- 为什么有两次判断空
此模式是为了解决线程安全问题,那么我们将视多线程为常态,我们假设只有只有一次判断为空,那么可能会有两个线程同时进入if语句中,这两个线程都会创建实例,只不过一个先创建一个后创建。所以需要再一次判断
if (uniqueInstance == null) {
synchronized (Singleton.class) {
uniqueInstance = new Singleton();
}
}
- 为什么设置返回变量uniqueInstance为volatile类型
大家都知道JMM有一个指令重排序的概念,而
uniqueInstance = new Singleton()
这个语句并不具有原子性,分为
- 为 uniqueInstance 分配内存空间
- 初始化 uniqueInstance
- 将 uniqueInstance 指向分配的内存地址
一旦第三步指令重排序到前面,那么得到的对象可能会是个空对象,而volatile修饰的变量具有禁止指令重排序的功能,所以避免了这种情况。