饿汉式(静态变量)
饿汉式(静态代码块)
双重检查(推荐使用)
静态内部类(推荐使用)
枚举(最好的方式)
new
)getInstance()
public class SingletonType1 {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2); // true
System.out.println(instance1.hashCode() == instance2.hashCode()); // true
}
}
class Singleton {
// 类中创建对象实例
private final static Singleton INSTANCE = new Singleton();
// 外部不能实例化
private Singleton() {
}
// 提供一个静态方法,返回实例化对象
public static Singleton getInstance() {
return INSTANCE;
}
}
优点:
缺点:
class Singleton {
// 静态代码块创建实例
private static Singleton INSTANCE;
static {
INSTANCE = new Singleton();
}
// 外部不能实例化
private Singleton() {
}
// 提供一个静态方法,返回实例化对象
public static Singleton getInstance() {
return INSTANCE;
}
}
总结:
class Singleton {
private static Singleton INSTANCE;
private Singleton() {
}
// 提供一个静态方法,返回实例化对象
public static Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
优点:
缺点:
if (INSTANCE == null)
判断语句块,还没来得及往下执行,另一个线程也通过了这个判断语句,这会导致产生多个实例,所以说线程不安全。class Singleton {
private static Singleton INSTANCE;
private Singleton() {
}
// 提供一个静态方法,返回实例化对象, 加入同步处理代码, 解决线程不安全的问题
public static synchronized Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
优点:
缺点:
getInstance()
方法都要进行同步。而其实这个方法只需要执行一次实例化代码就够了,后面想获取该类实例,直接 return
就可以了。方法进行同步效率太低。class Singleton {
private static Singleton INSTANCE;
private Singleton() {
}
// 提供一个静态方法,返回实例化对象, 加入同步处理代码块
public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
INSTANCE = new Singleton();
}
}
return INSTANCE;
}
}
总结:
synchronized
代码块没有意义,还是会产生多个实例。根本起不到线程同步的作用。class Singleton {
private static volatile Singleton INSTANCE;
private Singleton() {
}
public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
if (INSTANCE ==null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
INSTANCE = new Singleton();
实例化对象可以分为三个步骤:
然而有些编译器为了性能的原因,可能将第 2 步和第 3 步进行重排序,顺序就成了:
如果不加 volatile
这样就可能出现这种情况:
Time | 线程 A | 线程 B |
---|---|---|
T1 | 检查到 INSTANCE 为空 | |
T2 | 获取锁 | |
T3 | 再次检查到 INSTANCE 为空 | |
T4 | 为 INSTANCE 分配内存空间 | |
T5 | 将 INSTANCE 指向内存空间 | |
T6 | 检查到 INSTANCE 不为空 | |
T7 | 访问 INSTANCE ,(INSTANCE 此时未完成初始化) | |
T8 | 初始化 INSTANCE |
在这种情况下,T7 时刻线程 B 对 INSTANCE
的访问,访问的是一个初始化未完成的对象。
所以需要在 INSTANCE
前加上关键字 volatile
,使用了volatile
关键字后,重排序被禁止,所有的写(write)操作都将发生在读(read)操作之前。
总结:
if (INSTANCE == null)
检查,保证线程安全。if (INSTANCE == null)
直接 return
实例化对象,也避免了反复进行方法同步。Note:
双检锁中,volatile
和synchronized
的作用分别是什么?
- 首先我们直到两个关键字都可以实现可见性,那么在该例子中可见性由谁保证?
- 可见性由
synchronized
保证,volatile
最主要的功能不是可见性,而是防止指令重排
class Singleton {
private Singleton() {
}
// 静态内部类
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
总结:
getInstance()
方法时才会加载静态内部类,且只会装载一次。public class SingletonType8 {
public static void main(String[] args) {
Singleton instance1 = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println(instance1 == instance2); // true
System.out.println(instance1.hashCode() == instance2.hashCode()); // true
instance1.sayOk();
}
}
/**
* 枚举可以实现单例 (推荐使用)
*/
enum Singleton {
//属性
INSTANCE;
// 方法
public void sayOk() {
System.out.println("ok");
}
}
总结:
饿汉式写法:
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
new
。