文章目录
单例模式
单一实例,某个类在内存中只能存在单一实例对象,或者说一个VM实例中只有一个某个类的实例。
单例模式最大的特点就是节省系统开销,例如配置类、工具类这种“无状态”的类,可以设计为单例类。但是如果“有状态”如用户,则单例模式可能存在线程安全问题。
如果将一个有状态类设计为单例类,如用户,那么多个线程持有的引用将会指向单例对象,如果访问共享资源的方法不是“原子的”,那么就存在线程安全问题。
单例模式的基本实现:
【1】构造器是私有的,外界不能主动创建对象
【2】通过公有静态方法向外提供获取对象的方法
【3】单例类仅能创建一次单例对象,一旦创建便使用一个静态成员指针指向。
饿汉式
class Singleton{
private Singleton(){
}
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return Singleton.instance;
}
}
饿汉式,单例的创建是在类初始化阶段进行的,也就是在<clinit>()方法中进行的,该方法的线程安全由JVM保障(类初始化锁)。也可以将创建动作写在静态块中,本质上都是在<clinit>()方法进行初始化。
java规定,每一个类或者接口,都有一个唯一的初始化锁与之对应。JVM在类初始化过程中会获取这个初始化锁,并且每个线程至少获取一次锁来确保这个类已经被初始化过了,如果存在竞争那么未抢到类初始化锁的线程就会等待。
饿汉式的特点就是线程安全,这个线程安全是由JVM隐式实现的。
懒汉式
懒汉式的特点就是:延迟实例化,降低了初始化类或者创建实例的开销。
class Singleton{
private Singleton(){
}
private static Singleton instance;
public static Singleton getInstance(){
return new Singleton();
}
}
以上的写法在单线程环境下没有错误,但是多线程环境下会出现线程不安全问题。
【1】getInstance()方法不是同步方法,使得创建对象、返回引用这个动作不是原子的。因此可以创建大量对象
【2】多线程环境下,线程彼此之间存在可见性问题,因此无法及时感知到instance的变化
线程安全懒汉式
注意:这里的“线程安全”,指的是将“获得实例”这个动作封装为原子的动作,不存在“创建对象的时候,被其他线程干扰的情况”
如果单从java线程的角度思考,可以理解为“同步块内不进行切换”,但是java线程是对操作系统线程的进一步封装和抽象,因此即使线程进入同步块,操作系统层面仍然会发生切换,只不过java有意的为用户屏蔽这些细节——将注意力放在java线程而不是操作系统线程。
class Singleton{
private Singleton(){
}
private volatile static Singleton instance;
public static Singleton getInstance(){
if(instance==null){
synchronized (Singleton.class){
if(instance==null){
instance= new Singleton();
}
}
}
return instance;
}
}
volatile修饰instance,保证了instance对线程的看见性。当某一线程成功执行了instance赋值操作,那么其他所有线程都可以立即看到instance的最新值。
使用同步块包裹了操作共享变量instance的代码,同一时刻仅有一个线程会执行instance赋值操作。剩余一部分阻塞在synchronized对应的同步队列,一旦获得锁后,判断instance状态后得知已经指向对象,则退出。另一部分线程在外部发现instance已经指向对象,也将直接返回。
此外,此处的instance使用volatile修饰还有另外一个目的:禁止重排序
new 指令 可以 分解为【1】new【2】dup【3】invokeSpecial【4】a_store
大致对应:分配内存、初始化、返回引用
为了提升系统性能,处理器和编译器会在保证单线程运行正确的前提下(as-if-serial)对指令进行重排序,但是多线程环境下重排序可能导致出错。
例如,将new中的指令重排序后变成:分配内存、返回引用、初始化。
如果一个线程A在同步块中创建对象后,这时另外一个线程B便从外部拿到了这个未初始化对象的引用。
重排序在单线程环境下没问题,因为保证线程A正式访问这个instance之前,初始化操作完成即可。但是A访问instance之前,B先访问了instance,这时就出现问题了,B拿到了instance对象但是成员全部都是零值。
使用volatile可以返回引用与成员初始化之间重排序,保证线程拿到引用后,引用指向的对象已经完成初始化了。
基于静态内部类
class Singleton{
private Singleton(){
}
private static