单例模式的8种实现
前言
多态是面向对象的核心,理解了多态
才能进一步理解设计模式
才能进一步理解面向对象这种思想它的优势之所在
几乎全部的设计模式说到底都是多态 只有少数几种不是
现在手写单例的情况越来越少,一般我们都使用spring Bean工程给我们产生单例
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 = Mgr01.getInstance();
Mgr01 m2 = Mgr01.getInstance();
System.out.println(m1 == m2);
}
}
2、饿汉式-静态方法块
上例的另一种形式
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 = Mgr02.getInstance();
Mgr02 m2 = Mgr02.getInstance();
System.out.println(m1 == m2);
}
}
3、懒汉式
虽然达到了按需初始化的目的,但却带来线程不安全的问题
线程1判断完INSTANCE == null后,此时还没有实例化INSTANCE
刚好线程2进来了
由于两线程都会各自进行实例化
没有达到的单例模式目的,线程不安全
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<100; i++) {
new Thread(()->
System.out.println(Mgr03.getInstance().hashCode())
).start();
}
}
}
4、懒汉式 - synchronized锁住对象
可以通过synchronized解决线程不安全的问题,但也带来效率下降
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<100; i++) {
new Thread(()->{
System.out.println(Mgr04.getInstance().hashCode());
}).start();
}
}
}
5、懒汉式 - synchronized同步代码块
妄图通过减小同步代码块的方式提高效率,然后不可行
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<100; i++) {
new Thread(()->{
System.out.println(Mgr05.getInstance().hashCode());
}).start();
}
}
}
6、懒汉式 - if 双重检查
public class Mgr06 {
private static volatile Mgr06 INSTANCE; //JIT
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<100; i++) {
new Thread(()->{
System.out.println(Mgr06.getInstance().hashCode());
}).start();
}
}
}
7、静态内部类-完美解决方案
JVM保证单例
加载外部类时不会加载内部类,这样可以实现懒加载
public class Mgr07 {
private Mgr07() {
}
private static class Mgr07Holder {
private final static Mgr07 INSTANCE = new Mgr07();
}
public static Mgr07 getInstance() {
return Mgr07Holder.INSTANCE;
}
public static void main(String[] args) {
for(int i=0; i<100; i++) {
new Thread(()->{
System.out.println(Mgr07.getInstance().hashCode());
}).start();
}
}
}
8、枚举单例
不仅可以解决线程同步,还可以防止反序列化。
反序列化: 反射可以将.Class加载到内存,生成一个新的实例
枚举类没有构造方法,就算拿到Class文件,也没有办法构造这个对象
返回的只是静态属性,而它就是单例产生的那个对象
public enum Mgr08 {
INSTANCE;
public static void main(String[] args) {
for(int i=0; i<100; i++) {
new Thread(()->{
System.out.println(Mgr08.INSTANCE.hashCode());
}).start();
}
}
}