设计模式-单例模式
目标
- 掌握单例模式常见五种实现方式
- 了解 jdk 中有哪些地方体现了单例模式
实现方式
- 饿汉式
- 枚举饿汉式
- 懒汉式
- 双检锁懒汉式
- 内部类懒汉式
1、饿汉式单例
代码实现
java
package com.demo.singleton;
/**
* 饿汉式
*/
public class Singleton1 {
// 构造函数私有
private Singleton1() {
}
// 实例私有
private static final Singleton1 INSTANCE = new Singleton1();
// 提供获取实例的方法
public static Singleton1 getInstance() {
return INSTANCE;
}
}
测试单例
java
package com.demo.singleton;
public class Singleton1Test {
public static void main(String[] args) {
Singleton1 instance1 = Singleton1.getInstance();
Singleton1 instance2 = Singleton1.getInstance();
System.out.println(instance1);
// com.demo.singleton.Singleton1@66d3c617
System.out.println(instance2);
// com.demo.singleton.Singleton1@66d3c617
}
}
可以看到两次获得的实例是同一个对象
反射破坏单例
通过反射创建实例,破坏单例
java
package com.demo.singleton;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Singleton1Test {
public static void main(String[] args)
throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
Singleton1 instance1 = Singleton1.getInstance();
System.out.println(instance1);
// com.demo.singleton.Singleton1@66d3c617
// 通过反射创建实例,破坏单例
Singleton1 instance = Singleton1Test.reflection(Singleton1.class);
System.out.println(instance);
// com.demo.singleton.Singleton1@63947c6b
}
public static <T> T reflection(Class<T> clazz) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<T> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
return constructor.newInstance();
}
}
防止破坏单例模式,可以在构造函数中判断实例是否已经存在,如果存在则抛出异常
java
package com.demo.singleton;
/**
* 饿汉式
*/
public class Singleton1 {
// 构造函数私有
private Singleton1() {
// 防止破坏单例模式,
// 可以在构造函数中判断实例是否已经存在,如果存在则抛出异常
if(INSTANCE!=null){
throw new RuntimeException("单例对象不能重复创建");
}
}
// 实例私有
private static final Singleton1 INSTANCE = new Singleton1();
// 提供获取实例的方法
public static Singleton1 getInstance() {
return INSTANCE;
}
}
再次运行代码,输出如下
bash
com.demo.singleton.Singleton1@66d3c617
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.demo.singleton.Singleton1Test.reflection(Singleton1Test.java:23)
at com.demo.singleton.Singleton1Test.main(Singleton1Test.java:15)
Caused by: java.lang.RuntimeException: 单例对象不能重复创建
at com.demo.singleton.Singleton1.<init>(Singleton1.java:12)
... 6 more
反序列化破坏单例
通过反序列化创建实例,破坏单例
java
package com.demo.singleton;
import java.io.*;
public class Singleton1Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Singleton1 instance1 = Singleton1.getInstance();
System.out.println(instance1);
// com.demo.singleton.Singleton1@66d3c617
// 通过序列化和反序列化创建实例,破坏单例
Object instance2 = Singleton1Test.serializable(instance1);
System.out.println(instance2);
// com.demo.singleton.Singleton1@37a71e93
}
public static Object serializable(Object instance) throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(instance);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
return ois.readObject();
}
}
防止破坏单例, 在类中添加readResolve
方法
java
package com.demo.singleton;
import java.io.Serializable;
/**
* 饿汉式
*/
public class Singleton1 implements Serializable {
// 构造函数私有
private Singleton1() {
if (INSTANCE != null) {
throw new RuntimeException("单例对象不能重复创建");
}
}
// 实例私有
private static final Singleton1 INSTANCE = new Singleton1();
// 提供获取实例的方法
public static Singleton1 getInstance() {
return INSTANCE;
}
// 反序列化时,如果存在这个方法,则会调用这个方法,而不是返回新的对象
public Object readResolve(){
return INSTANCE;
}
}
再次运行代码,输出结果如下
bash
com.demo.singleton.Singleton1@66d3c617
com.demo.singleton.Singleton1@66d3c617
unsafe破坏单例
通过unsafe创建实例,破坏单例
引入spring依赖
xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.31</version>
</dependency>
代码实现
java
package com.demo.singleton;
import org.springframework.objenesis.instantiator.util.UnsafeUtils;
public class Singleton1Test {
public static void main(String[] args)
throws InstantiationException {
Singleton1 instance1 = Singleton1.getInstance();
System.out.println(instance1);
// com.demo.singleton.Singleton1@66d3c617
// 通过unsafe创建实例,破坏单例
Object instance = Singleton1Test.unsafe(Singleton1.class);
System.out.println(instance);
// com.demo.singleton.Singleton1@4d7e1886
}
public static Object unsafe(Class<?> clazz) throws InstantiationException {
return UnsafeUtils.getUnsafe().allocateInstance(clazz);
}
}
预防手段:暂无
2、枚举饿汉式单例
代码实现
java
package learn.singleton;
public enum Singleton2 {
INSTANCE;
// 提供获取实例的方法
public static Singleton2 getInstance() {
return INSTANCE;
}
@Override
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
}
测试单例
java
package learn.singleton;
public class Singleton2Test {
public static void main(String[] args) {
Singleton2 instance1 = Singleton2.getInstance();
Singleton2 instance2 = Singleton2.getInstance();
System.out.println(instance1);
// learn.singleton.Singleton2@6504e3b2
System.out.println(instance2);
// learn.singleton.Singleton2@6504e3b2
}
}
反射破坏单例
通过反射创建实例,不能破坏单例
java
package learn.singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Singleton2Test {
public static void main(String[] args)
throws InstantiationException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Singleton2 instance1 = Singleton2.getInstance();
System.out.println(instance1);
// learn.singleton.Singleton2@6504e3b2
// 通过反射创建实例,破坏单例
Object instance = Singleton2Test.reflection(Singleton2.class);
}
public static <T> T reflection(Class<T> clazz) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, InvocationTargetException {
Constructor<T> constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
return constructor.newInstance("INSTANCE", 0);
}
}
使用无参构造会报错NoSuchMethodException
java
Constructor<T> constructor = clazz.getDeclaredConstructor();
bash
Exception in thread "main" java.lang.NoSuchMethodException: learn.singleton.Singleton2.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at learn.singleton.Singleton2Test.reflection(Singleton2Test.java:23)
at learn.singleton.Singleton2Test.main(Singleton2Test.java:16)
因为枚举类的父类是Enum,有2个参数的构造函数
java
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
正确获取构造函数的方法
java
Constructor<T> constructor = clazz.getDeclaredConstructor(String.class, int.class);
反射获取枚举单例,最后还是会报错Cannot reflectively create enum objects
bash
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at learn.singleton.Singleton2Test.reflection(Singleton2Test.java:25)
at learn.singleton.Singleton2Test.main(Singleton2Test.java:16)
反序列化破坏单例
通过反序列化创建实例,不能破坏单例
反序列化得到的对象,和原有实例相等
java
package learn.singleton;
import java.io.*;
public class Singleton2Test {
public static void main(String[] args)
throws IOException, ClassNotFoundException {
Singleton2 instance1 = Singleton2.getInstance();
System.out.println(instance1);
// learn.singleton.Singleton2@6504e3b2
// 通过反射创建实例,破坏单例
Object instance2 = Singleton2Test.serializable(instance1);
System.out.println(instance2);
// learn.singleton.Singleton2@6504e3b2
System.out.println(instance1 == instance2);
// true
}
public static Object serializable(Object instance) throws ClassNotFoundException, IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(instance);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
return ois.readObject();
}
}
unsafe破坏单例
通过unsafe创建实例,可以实现破坏单例
java
package learn.singleton;
import org.springframework.objenesis.instantiator.util.UnsafeUtils;
public class Singleton2Test {
public static void main(String[] args)
throws InstantiationException {
Singleton2 instance1 = Singleton2.getInstance();
System.out.println(instance1);
// learn.singleton.Singleton2@6504e3b2
// 通过反射创建实例,破坏单例
Object instance2 = Singleton2Test.unsafe(Singleton2.class);
System.out.println(instance2);
// learn.singleton.Singleton2@123a439b
}
public static Object unsafe(Class<?> clazz) throws InstantiationException {
return UnsafeUtils.getUnsafe().allocateInstance(clazz);
}
}
3、懒汉式单例
代码实现
java
package learn.singleton;
import java.io.Serializable;
/**
* 懒汉式单例
*/
public class Singleton3 implements Serializable {
// 构造函数私有
private Singleton3() {}
// 实例私有
private static Singleton3 INSTANCE = null;
// 提供获取实例的方法
public static Singleton3 getInstance() {
if(INSTANCE == null){
INSTANCE = new Singleton3();
}
return INSTANCE;
}
}
单例测试
java
package learn.singleton;
public class Singleton3Test {
public static void main(String[] args) {
Singleton3 instance1 = Singleton3.getInstance();
Singleton3 instance2 = Singleton3.getInstance();
System.out.println(instance1);
// learn.singleton.Singleton3@6504e3b2
System.out.println(instance2);
// learn.singleton.Singleton3@6504e3b2
}
}
线程安全
多线程获取单例,会出现多个实例
java
package learn.singleton;
public class Singleton3Test {
public static void main(String[] args) {
// 线程1
new Thread(new Runnable() {
@Override
public void run() {
Singleton3 instance1 = Singleton3.getInstance();
System.out.println(instance1);
// learn.singleton.Singleton3@5b8d42e2
}
}).start();
// 线程2
new Thread(new Runnable() {
@Override
public void run() {
Singleton3 instance2 = Singleton3.getInstance();
System.out.println(instance2);
// learn.singleton.Singleton3@44e2d7cc
}
}).start();
}
}
解决办法:通过增加synchronized
关键字
java
package learn.singleton;
import java.io.Serializable;
/**
* 懒汉式单例
*/
public class Singleton3 implements Serializable {
// 构造函数私有
private Singleton3() {}
// 实例私有
private static Singleton3 INSTANCE = null;
// 提供获取实例的方法
public static synchronized Singleton3 getInstance() {
if(INSTANCE == null){
// 线程耗时
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Singleton3();
}
return INSTANCE;
}
}
再次运行多线程获取单例对象,则不会出现多个实例
learn.singleton.Singleton3@b6e42e3
learn.singleton.Singleton3@b6e42e3
4、懒汉式单例-DCL
双检锁 Double-Check Locking
java
package learn.singleton;
import java.io.Serializable;
/**
* 懒汉式单例-DCL
*/
public class Singleton3 implements Serializable {
// 构造函数私有
private Singleton3() {
}
// 实例私有
private static volatile Singleton3 INSTANCE = null;
// 提供获取实例的方法
public static Singleton3 getInstance() {
if (INSTANCE == null) {
synchronized (Singleton3.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton3();
}
}
}
return INSTANCE;
}
}
volatile的作用
volatile 可见性 有序性
bash
# 反编译
javap -c -v -p xxx.class
指令重排序示意图
volatile修饰共享变量,可以阻止指令重排序
5、懒汉式单例-内部类
代码实现
java
package learn.singleton;
import java.io.Serializable;
/**
* 懒汉式单例-内部类
*/
public class Singleton5 implements Serializable {
// 构造函数私有
private Singleton5() {
System.out.println("call constructor");
}
private static class Holder{
// 实例私有
static Singleton5 INSTANCE = new Singleton5();
}
// 提供获取实例的方法
public static Singleton5 getInstance() {
return Holder.INSTANCE;
}
public static void method(){
System.out.println("call method");
}
}
测试
java
package learn.singleton;
public class Singleton5Test {
public static void main(String[] args) {
Singleton5.method();
Singleton5 instance1 = Singleton5.getInstance();
System.out.println(instance1);
// learn.singleton.Singleton5@6504e3b2
Singleton5 instance2 = Singleton5.getInstance();
System.out.println(instance2);
// learn.singleton.Singleton5@6504e3b2
}
}
输出结果
call method
call constructor
learn.singleton.Singleton5@6504e3b2
learn.singleton.Singleton5@6504e3b2
由static
修饰的静态代码块,不会有线程安全问题
JDK中的单例模式
Runtime.java
java
package java.lang;
public class Runtime {
private Runtime() {}
// 饿汉式单例
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
}
System.java
java
package java.lang;
public final class System {
private System() {}
// 双检锁单例
private static volatile Console cons = null;
public static Console console() {
if (cons == null) {
synchronized (System.class) {
cons = sun.misc.SharedSecrets.getJavaIOAccess().console();
}
}
return cons;
}
}
Collections.java
java
package java.util;
public class Collections {
private Collections() {}
// 饿汉式单例
public static final Set EMPTY_SET = new EmptySet<>();
public static final <T> Set<T> emptySet() {
return (Set<T>) EMPTY_SET;
}
// 饿汉式单例
public static final List EMPTY_LIST = new EmptyList<>();
public static final <T> List<T> emptyList() {
return (List<T>) EMPTY_LIST;
}
// 饿汉式单例
public static final Map EMPTY_MAP = new EmptyMap<>();
public static final <K,V> Map<K,V> emptyMap() {
return (Map<K,V>) EMPTY_MAP;
}
// 内部类懒汉式
public static final <K,V> NavigableMap<K,V> emptyNavigableMap() {
return (NavigableMap<K,V>) UnmodifiableNavigableMap.EMPTY_NAVIGABLE_MAP;
}
static class UnmodifiableNavigableMap<K,V> {
private static final EmptyNavigableMap<?,?> EMPTY_NAVIGABLE_MAP =
new EmptyNavigableMap<>();
}
}
Collections.java
java
package java.util;
public class Collections {
private static class ReverseComparator
@Override
public Comparator<Comparable<Object>> reversed() {
return Comparator.naturalOrder();
}
}
}
public interface Comparator<T> {
public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
}
}
class Comparators {
private Comparators() {
throw new AssertionError("no instances");
}
// 枚举实现单例
enum NaturalOrderComparator implements Comparator<Comparable<Object>> {
INSTANCE;
}
}