写在最前面
单例的文章很多,所以本文主要想记录下几种常用单例的不好理解之处
- 双重检测 静态资源上的volatile 关键字作用 ?
- 静态内部类怎么实现线程安全单例 ?
- 枚举单例怎么理解 ?
单例的概念
-
单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的一个类只有一个实例。即一个类只有一个对象实例。
-
Java中单例模式定义:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。”
详情见百度百科
单例的特点
- 一是某个类只能有一个实例
- 二是它必须自行创建这个实例
- 三是它必须自行向整个系统提供这个实例
详情见百度百科
单例的几种写法
- 饿汉模式 (线程安全 低效)
/**
* 类加载时就创建实例
*/
public class Singleton {
private Singleton(){}
private final static Singleton singleton= new Singleton();
public static Singleton getInstance(){
return singleton;
}
}
/**
* 静态代码块实现(类加载时)
*/
public class Singleton {
private Singleton() {}
private static Singleton singleton;
static {
singleton= new Singleton();
}
public static Singleton getInstance() {
return singleton;
}
}
- 懒汉模式 (线程不安全)
public class Singleton {
private Singleton(){}
private static Singleton singleton;
public static Singleton getInstance() {
if (null == singleton) {
singleton = new Singleton();
}
return singleton;
}
}
- 懒汉加锁 (线程安全 低效)
/**
* 方法加锁
*/
public class Singleton {
private Singleton() {}
private static Singleton singleton;
public static synchronized Singleton getInstance() {
if (null == singleton) {
singleton = new Singleton();
}
return singleton;
}
}
/**
* 代码块加锁
*/
public class Singleton {
private Singleton() {}
private static Singleton singleton;
public static Singleton getInstance() {
if (null == singleton) {
synchronized (Singleton.class) {
singleton = new Singleton();
}
}
return singleton;
}
}
- 双重检测 (线程安全 高效)
创建实例代码 singleton = new Singleton(); 主要做了三件事
1.开辟空间
2.创建对象
3.地址指向
不加volatile:可能执行顺序为132 这时第二个获取资源会是地址不为空但是对象实例为空
加上volatile:防止JVM指令重排 确保执行顺序为123 确保要么静态变量为NULL 要么有地址指向并且对象实例也不为空
public class Singleton {
private Singleton() {}
private static volatile Singleton singleton;
public static Singleton getInstance() {
if (null == singleton) {
synchronized (Singleton.class) {
if (null == singleton) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
- 静态内部类 (线程安全 高效)
静态内部类是怎么保证线程安全的呢?
虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,
那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。
如果在一个类的<clinit>()方法中有耗时很长的操作,就可能造成多个进程阻塞 ( 需要注意的是,其他线程虽然会被阻塞,
但如果执行<clinit>()方法后,其他线程唤醒之后不会再次进入<clinit>()方法。同一个加载器下,一个类型只会初始化一次。)
在实际应用中,这种阻塞往往是很隐蔽的
public class Singleton {
private Singleton() {}
private static class SingletonInner {
private static final Singleton singleton= new Singleton();
}
public static Singleton getInstance() {
return SingletonInner.singleton;
}
}
- 枚举 (线程安全 高效)
枚举实现单例可能不太好理解
其实在编译成class文件时,会默认为枚举类成员加上 final static修饰
再想想就和静态内部类的实现几乎一样了
在实际代码中可以将需要执行一次的可写在私有方法中
public enum Singleton {
INSTANCE;
public void methodA() {
}
// 枚举中私有方法只会执行一次
private void methodB() {
}
}