一 单例的演变
a 饿汉式
1 代码
/**
* 饿汉式
* 类加载到内存后,就实例化一个单例,JVM 保证线程安全
* 简单实用,推荐使用
* 唯一缺点:不管用到与否,类装载时就完成实例化。
*/
public class Mgr01 {
private static final Mgr01 INSTANCE = new Mgr01();
private Mgr01() {
}
public static Mgr01 getInstance() {
return INSTANCE;
}
public static void main(String[] args) {
Mgr01 m1 = getInstance();
Mgr01 m2 = getInstance();
System.out.println(m1 == m2);
}
}
2 测试
true
b 通过静态代码块实现
1 代码
public class Mgr02 {
private static final Mgr02 INSTANCE;
static {
INSTANCE = new Mgr02();
}
private Mgr02() {
}
public static Mgr02 getInstance() {
return INSTANCE;
}
public static void main(String[] args) {
Mgr02 m1 = getInstance();
Mgr02 m2 = getInstance();
System.out.println(m1 == m1);
}
}
2 测试
true
c 懒汉式——非线程安全
1 代码
/**
* 懒汉式:什么时候用,什么时候初始化
* 非线程安全
*/
public class Mgr03 {
private static Mgr03 INSTANCE;
private Mgr03() {
}
public static Mgr03 getInstance() {
if (INSTANCE == null) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Mgr03();
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
System.out.println(Mgr03.getInstance().hashCode());
}).start();
}
}
}
2 测试
2033291474
823004921
2118558849
1273086253
1851926792
1309188278
1851926792
157337118
270427352
11204854
d 懒汉式——线程安全
1 代码
/**
* Lazy loading
* 懒汉式:什么时候用,什么时候初始化
* 可以通过 synchronized 解决非线程安全问题,但是也让效率降低了很多。
*/
public class Mgr04 {
private static Mgr04 INSTANCE;
private Mgr04() {
}
public static synchronized Mgr04 getInstance() {
if (INSTANCE == null) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Mgr04();
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
System.out.println(Mgr04.getInstance().hashCode());
}).start();
}
}
}
2 测试
11204854
11204854
11204854
11204854
11204854
11204854
11204854
11204854
11204854
11204854
e 加锁代码细化,从锁整个方法下移到方法体内某个代码片段
1 代码
/**
* 判断之后,给代码块加锁,任然不能解决线程安全问题
*/
public class Mgr05 {
private static Mgr05 INSTANCE;
private Mgr05() {
}
public static Mgr05 getInstance() {
if (INSTANCE == null) {
synchronized (Mgr05.class) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Mgr05();
}
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> System.out.println(Mgr05.getInstance().hashCode())).start();
}
}
}
2 测试
2118558849
270427352
823004921
1273086253
1851926792
157337118
1309188278
2033291474
1152183952
11204854
f 懒汉式——双重判断可以保证线程安全
1 代码
/**
* 懒汉式,双重判断可以保证线程安全
*/
public class Mgr06 {
// 在非超高并发情况下,虽然不加 volatile,代码也能运行正确,这里一定要加 volatile,防止指令重排,在超高并发情况下出问题
private static volatile Mgr06 INSTANCE;
private Mgr06() {
}
public static Mgr06 getInstance() {
if (INSTANCE == null) {
//双重检查
synchronized (Mgr06.class) {
if (INSTANCE == null) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Mgr06();
}
}
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> System.out.println(Mgr06.getInstance().hashCode())).start();
}
}
}
2 测试
11204854
11204854
11204854
11204854
11204854
11204854
11204854
11204854
11204854
11204854
二 小结
1 实例化一个对象三步走
2 代码证明
3 如果发生指令重排序,比如先执行第 1 步,再执行第 3 步,最后执行第 2 步,在执行完第 3 步后,INSTANCE 里变量的值就是 0 了,会出现问题。