1.什么是单例设计模式:?
单例设计模式:确保一个类只有一个实例,并提供一个全局访问点.
2.为什么要用单例设计模式?
对于系统中的某些类来说,只有一个实例很重要,如任务管理器,回收站等.所以单例设计模式是为了解决只需要一个实例这种情景下的一种解决方案.
3.单例设计模式的优缺点?
优点:因为只能创建一个对象,所以节省内存资源,并且易于管理
缺点:不适用于状态变化的对象,因为当对象的状态在不同场景下发生变化时,单例会引起数据的错误,不能保证彼此的状态.如果要解决这个问题,需要使用多例模式.
4.单例设计模式如何实现?
单例设计模式分为两种:懒汉式和饿汉式,懒汉式就是延迟加载的单例设计模式.
饿汉式实现:
public class Singleton {
private static final Singleton singleton = new Singleton();
// 私有化构造函数,防止外部创建对象
private Singleton() {
}
// 提供一个全局访问点.因为外部不能实例化对象,所以方法必须是静态的,
// 因此成员也必须是静态的
public static Singleton getSingleton() {
return singleton;
}
}
特点:只要类被加载就就会初始化成员,类加载较慢,获取实例很快.因为饿汉式有final关键字修饰,所以是线程安全的,不会产生this引用逃逸的现象.
懒汉式实现:
public class Singleton {
private static Singleton singleton = null;
// 私有化构造函数,防止外部创建对象
private Singleton() {
}
// 提供一个全局访问点.因为外部不能实例化对象,所以方法必须是静态的,
// 因此成员也必须是静态的
public static Singleton getSingleton() {
if (singleton==null) {
singleton = new Singleton();
}
return singleton;
}
}
特点:在第一次使用get方法时才会创建对象,获取实例较慢.从资源利用角度看,延迟加载挺好.
当多线程时.懒汉式会有安全问题,因此需要进行同步:
线程安全的懒汉式:
public class Singleton {
private static Singleton singleton = null;
// 私有化构造函数,防止外部创建对象
private Singleton() {
}
// 提供一个全局访问点.因为外部不能实例化对象,所以方法必须是静态的,
// 因此成员也必须是静态的
public static synchronized Singleton getSingleton() {
if (singleton==null) {
singleton = new Singleton();
}
return singleton;
}
}
这种方式有一个极大的缺点就是在每次进行获取单例对象时都需要进行同步,同步会导致操作系统从用户态切换为核心态,时间开销很大,因此懒汉式又有了DCL(双重检查锁定)形态.
DCL形态:
public class Singleton {
private static volatile Singleton singleton = null;
// 私有化构造函数,防止外部创建对象
private Singleton() {
}
// 提供一个全局访问点.因为外部不能实例化对象,所以方法必须是静态的,
// 因此成员也必须是静态的
public static Singleton getSingleton() {
if (singleton==null) {
synchronized(Singleton.class) {
if (singleton==null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
这种形式里层的if用于判断成员是否还未创建,如果未创建,则创建对象,因为if代码在同步代码块中,可以保证多线程下只会创建一个实例对象,不会重复创建覆盖.但如果不加外层的if判断,每次获取对象时都会进入同步,时间开销大,所以加上外层if判断可以保证同步代码块中创建对象的操作只执行一次,并且执行完成后,以后每次在获取对象时都无需进行同步操作,极大提高了系统性能.
为什么要为单例成员对象加volatile关键字?
首先简单介绍一下this引用逃逸,所谓this引用逃逸就是指多线程环境下,对象的构造方法未彻底完成前,将自身的引用向外发布了,其他线程则可能会访问到未初始化完成或者初始化完成了一半的对象.
然后我们介绍一下volatile关键字:
volatile关键字有两个语义:
1.保证内存的可见性:在多线程环境下同步变量, 线程为了提高效率,会将某成员变量从主内存中拷贝到工作内存(其实就是缓存)中进行修改,修改完成后再将工作内存中的数据存入主内存的真实变量中,因此多线程情况下有可能产生数据一致性问题,volatile就是用来避免这种情况的。volatile告诉jvm, 它所修饰的变量不保留拷贝,直接访问主内存中的变量.
2.禁止指令重排序:指令重排序是一种优化策略,是为了尽可能的利用CPU资源而进行的一种优化(即写在前面的代码指令在时间顺序上有可能会在写在后面的代码指令执行后再被执行),优化分为两个层面,JVM虚拟机进行的指令重排序优化和CPU硬件自身进行的指令重排序优化,指令重排序的根本原因就是因为CPU执行速度太快,而虚拟机的工作内存和计算机的缓存速度太慢.
如果不给成员添加volatile关键字,因为有指令重排序优化的存在,所以多线程环境下,有可能会产生this引用逃逸的情况,因此要给成员添加volatile关键字.
最后,来讲一下静态内部类形式的单例模式,这种形式,局部看是饿汉式,整体看是懒汉式,兼顾了饿汉式的安全性,又具备了懒汉式的延迟加载特性,并且不需要加同步,性能高.
public class Singleton {
private Singleton() {}
static class Inner{
private static final Singleton instance = new Singleton();
}
public static final Singleton getInstance() {
return Inner.instance;
}
}
给实例加上final或者volatile都可以保证数据的可见性,因此不会产生引用逃逸和线程安全问题.