设计模式(一)单例模式
- 一、什么是单例模式
- 二、单例模式的8种写法
- (1)饿汉式
- (2)静态语句块实现
- (3)懒汉式(lazy loading)
- (4)懒汉式升级版
- (5)在方法4的基础上在升级
- (6)懒汉式完美级写法(双重检查)
- (7)单例最完美的写法
- (8)完美中的完美写法
一、什么是单例模式
单例模式是java设计模式中比较常见的一种设计模式,本文介绍8中单例模式的创建以及发展流程。
1.特点
(1)单例类只能有一个实例。
(2)单例类必须自己创建自己的唯一实例。
(3)单例类必须给所有其他对象提供这一实例。
二、单例模式的8种写法
(1)饿汉式
类加载到内存后,就会实例一个单例,JVM保证线程安全
特点:
简单实用,推荐使用,但是不管是否用到,类加载时完成实例化
public class Mgr01 {
private static final Mgr01 INSTANCE=new Mgr01();
private Mgr01(){}
public static Mgr01 getINSTANCE() {
return INSTANCE;
}
public void m(){
System.out.println("m");
}
public static void main(String[] args) {
Mgr01 m1=Mgr01.getINSTANCE();
Mgr01 m2=Mgr01.getINSTANCE();
System.out.println(m1==m2);
}
}
注意:
如果在其他类中加载不能直接new
,需要直接调用getINSTANCE()
方法
public class SingletonMain {
public static void main(String[] args) {
Mgr01 mgr01=Mgr01.getINSTANCE();
mgr01.m();;
}
}
(2)静态语句块实现
和饿汉式差不多的实现,主要是通过静态语句块来进行初始化
public class Mgr02 {
private static final Mgr02 INSTANCE;
static {
INSTANCE = new Mgr02();
}
public static Mgr02 getINSTANCE() {
return INSTANCE;
}
public void m() {
System.out.println("m");
}
}
(3)懒汉式(lazy loading)
特点:什么时候需要,什么时候加载,但是线程不安全
public class Mgr03 {
private static Mgr03 INSTANCE;
private Mgr03() {
}
//多线程访问的时候,会影响
public static Mgr03 getINSTANCE() {
if (INSTANCE == null) {
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Mgr03();
}
return INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
//测试,hashcode会出现不同,相同对象的hashcode相同
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
System.out.println(Mgr03.getINSTANCE().hashCode());
}).start();
}
}
}
(4)懒汉式升级版
为了解决懒汉式的线程安全问题,给getINSTANCE
方法加上了锁,虽然解决了线程安全问题,但是降低了效率
public class Mgr04 {
//优化版,加锁,效率低了
private static Mgr04 INSTANCE;
private Mgr04() {
}
public static synchronized Mgr04 getINSTANCE() {
if (INSTANCE == null) {
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Mgr04();
}
return INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
//测试,hashcode会出现不同,相同对象的hashcode相同
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
System.out.println(Mgr04.getINSTANCE().hashCode());
}).start();
}
}
}
(5)在方法4的基础上在升级
经过(4)的优化后,带来了效率低的问题。就有人问了,能不能有既保证线程安全的情况下,又提高了效率呢?所有又有了一个升级版,但是却不行
public class Mgr05 {
private static Mgr05 INSTANCE;
private Mgr05() {
}
//多线程访问的时候,会影响
public static Mgr05 getINSTANCE() {
if (INSTANCE == null) {
//妄图通过减小同步代码块的方式提高效率,然后不可行
synchronized (Mgr05.class){
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Mgr05();
}
}
return INSTANCE;
}
public static void main(String[] args) {
//测试,hashcode会出现不同,相同对象的hashcode相同
for (int i = 0; i < 1000; i++) {
new Thread(()->{
System.out.println(Mgr05.getINSTANCE().hashCode());
}).start();
}
}
}
不行的原因:当线程1还未结束的时候,线程2进入了方法,这时候,线程1未结束,INSTANCE 还是为空的,这时候线程2,就会跳过这个判断,等待线程1结束后,再创建实例
(6)懒汉式完美级写法(双重检查)
这时候就有人想吹毛求疵了,能不能解决上面的问题,实现单例呢?有
public class Mgr06 {
private static volatile Mgr06 INSTANCE;
private Mgr06() {
}
//完美写法
public static Mgr06 getINSTANCE() {
if (INSTANCE == null) {
//双重检查
synchronized (Mgr06.class) {
if (INSTANCE == null) {
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Mgr06();
}
}
}
return INSTANCE;
}
public static void main(String[] args) {
//测试,hashcode会出现不同,相同对象的hashcode相同
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
System.out.println(Mgr06.getINSTANCE().hashCode());
}).start();
}
}
}
补充:通过双重检查,就能完美解决线程安全问题。细心的小伙伴就发现了,能不能直接写一个检查,写在方法内部?答案肯定是不行的。为啥呢?当然为了效率…
(7)单例最完美的写法
使用静态内部类的方法,JVM保证了线程安全,加载外部类的时候不会加载内部类,这样可以实现懒加载。最完美的写法,比饿汉式完美。只有去调用getInstance
时才去加载实例
public class Mgr07 {
private Mgr07() {
}
private static class Mgr07Holder {
private final static Mgr07 INSTANCE = new Mgr07();
}
//调用getInstance时才去加载实例
public static Mgr07 getInstance() {
return Mgr07Holder.INSTANCE;
}
public static void main(String[] args) {
//测试,hashcode会出现不同,相同对象的hashcode相同
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
System.out.println(Mgr07.getInstance().hashCode());
}).start();
}
}
}
(8)完美中的完美写法
这种写法很少用到,但确实理想中的完美级。不仅可以解决线程安全问题,还可以防止反序列化。主要原因:枚举类没有构造方法
public enum Mgr08 {
INSTANCE;
public static void main(String[] args) {
//测试,hashcode会出现不同,相同对象的hashcode相同
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
System.out.println(Mgr08.INSTANCE.hashCode());
}).start();
}
}
}