1. 饿汉式(线程安全)
容易产生垃圾对象。
public class Singleton1 {
private static final Singleton1 instance = new Singleton1();
private Singleton1() {}
public static Singleton1 getInstance() {
return instance;
}
}
**2. 懒汉式(线程不安全) **
public class SingleTon {
private static SingleTon instance;
private SingleTon() {}
public static SingleTon getInstance() {
if (instance == null) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new SingleTon();
}
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> System.out.println(SingleTon.getInstance().hashCode())).start();
}
}
}
/** hashCode不一致,不是一个单例
1236783213
1849980617
2137033978
270921682
1902102109
919115337
1482438336
587167875
932094492
2016912823
*/
3. 懒汉式(线程安全)
但是,对整个方法加锁,锁的粒度太大,效率比较低
public class SingleTon {
private static SingleTon instance;
private SingleTon() {}
public static synchronized SingleTon getInstance() {
if (instance == null) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new SingleTon();
}
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> System.out.println(SingleTon.getInstance().hashCode())).start();
}
}
}
/** hashCode一致
1482438336
1482438336
1482438336
1482438336
1482438336
1482438336
1482438336
1482438336
1482438336
1482438336
*/
4. 懒汉式(优化加锁,但不可行)
public class SingleTon {
private static SingleTon instance;
private SingleTon() {}
public static SingleTon getInstance() {
if (instance == null) {
// 对里面进行加锁,虽然可以提高效率,但是不可行,不是单例模式
/**
* 当两个线程同时运行到此处时
* 第一个线程假设进入同步代码块获得了实例
* 第二个线程这时也可以进入同步代码块获取实例
* 所以,可以在synchronized中再次判断instance是否为空
*/
synchronized(SingleTon.class) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new SingleTon();
}
}
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> System.out.println(SingleTon.getInstance().hashCode())).start();
}
}
}
/** hashCode不一致
1236783213
1482438336
919115337
2113902944
1849980617
270921682
932094492
2016912823
587167875
1902102109
*/
5. 懒汉式(双重校验锁,线程安全)
public class SingleTon {
private static SingleTon instance;
private SingleTon() {}
public static SingleTon getInstance() {
if (instance == null) { // 第一次判空:可以排除大部分instance不为空的情况,可以提高效率
synchronized(SingleTon.class) {
if (instance == null) { // 防止两个线程同时运行到此处
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new SingleTon();
}
}
}
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> System.out.println(SingleTon.getInstance().hashCode())).start();
}
}
}
/** hsahCode一致
1482438336
1482438336
1482438336
1482438336
1482438336
1482438336
1482438336
1482438336
1482438336
1482438336
*/
在第2行代码处要不要加volatile
呢?
需要注意 instance
采⽤ volatile
关键字修饰也是很有必要。
instance = new Singleton();
这段代码其实是分为三步执⾏:
- 为
instance
分配内存空间 - 初始化
instance
- 将
instance
指向分配的内存地址
但是由于 JVM 具有指令重排的特性,执⾏顺序有可能变成 1>3>2
。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致⼀个线程获得还没有初始化的实例。例如,线程 T1 执⾏了 1 和 3,此时 T2 调⽤ getInstance() 后发现 instance
不为空,因此返回instance
,但此时 instance
还未被初始化。
使⽤ volatile
可以禁⽌ JVM 的指令重排,保证在多线程环境下也能正常运⾏。