1.概要
分类 | 优劣 |
饿汉式 | 线程安全,效率高,不支持懒加载 |
懒汉式 | 线程安全,效率低,支持懒加载 |
双重检索式 | 线程安全,效率高,支持懒加载(不建议使用,会出现问题) |
静态内部类式 | 线程安全,效率高,支持懒加载 |
枚举式 | 线程安全,效率高,不支持懒加载,天然防止反射和序列化反序列化 |
2.代码案例
// 单例模式之饿汉单例
public class Demo1 {
private static Demo1 instance = new Demo1();//内部封装一个实例,创建类的时候立即生成,这是饿汉式之所以为饿汉式的关键
private Demo1(){//私有化构造器,使之无法生成实例
}
public static Demo1 getInstance(){//使用静态使得在不能创建对象的情况下依旧可以调用方法,从而通过这个入口返回一个实例对象
return instance;
}
}
// 单例模式之懒汉单例
public class Demo2 {
private static Demo2 instance;//没有立即生成实例,这是懒汉式的关键
private Demo2(){}//私有构造是单例的基础
public static synchronized Demo2 getInstance(){//因为是同步的原因所以效率低下
if (null == instance){
instance = new Demo2();
}
return instance;
}
}
// 单例模式之双重检查锁单例(没懂)不建议使用,会出现问题
public class Demo3 {
private static Demo3 instance = null;
private Demo3(){}
public static Demo3 getInstance(){
if (null == instance){
Demo3 demo3;
synchronized (Demo3.class){
demo3 = instance;
if (null == demo3){
synchronized (Demo3.class){
demo3 = new Demo3();
}
}
}
instance = demo3;
}
return instance;
}
}
// 单例模式之静态内部类单例
public class Demo4 {
private Demo4(){
}
private static class Temp{
private static Demo4 instance = new Demo4();
}
public static Demo4 getInstance(){//获取实例的方式不同了
return Temp.instance;
}
}
// 单例模式之枚举单例
public enum Demo5 {
INSTANCE;
public void method(){
}
}
// 多线程状况下测试各模式的效率
public class Client5 {
public static void main(String[] args) throws InterruptedException {
long startTime = System.currentTimeMillis();//开始时间
int threadNum = 10000;
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);//用于多线程测试的关键
for (int i = 0; i < threadNum; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
Demo5.INSTANCE.method();
}
}
}).start();
countDownLatch.countDown();
}
countDownLatch.await();
long endTime = System.currentTimeMillis();//结束时间
System.out.println("耗时:" + (endTime - startTime));
}
}
// 判断是否是单例
public class Client6 {
public static void main(String[] args) {
System.out.println(Demo1.getInstance() == Demo1.getInstance());
System.out.println(Demo2.getInstance() == Demo2.getInstance());
System.out.println(Demo3.getInstance() == Demo3.getInstance());
System.out.println(Demo4.getInstance() == Demo4.getInstance());
System.out.println(Demo5.INSTANCE == Demo5.INSTANCE);
}
}
3.漏洞:反射/序列化反序列化破解单例
反射破解:
// 反射
Class<Demo1> clazz = Demo1.class;
Constructor constructor = clazz.getDeclaredConstructor(null);
constructor.setAccessible(true);
Demo1 demo1 = (Demo1) constructor.newInstance();
System.out.println(demo1==Demo1.getInstance());
反射预防:
// 单例模式之饿汉单例
public class Demo1 {
private static Demo1 instance = new Demo1();
private Demo1(){
// 添加异常处理
if (null != instance){
throw new RuntimeException();
}
}
public static Demo1 getInstance(){
return instance;
}
}
序列化反序列化破解:
// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("Java/src/com/tlx/design/creationalPattern/singletonPattern/text.txt")));
oos.writeObject(Demo1.getInstance());
oos.close();
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("Java/src/com/tlx/design/creationalPattern/singletonPattern/text.txt")));
Demo1 demo11 = (Demo1)ois.readObject();
System.out.println(demo11==Demo1.getInstance());
ois.close();
// 目标类必须实现Serializable接口才可以用序列化
防止序列化反序列化破解:
public Object readResolve() throws ObjectStreamException {
return instance;
}
//在目标类中添加如下方法,在序列化时他会检查目标类有没有该方法,如果没有会执行默认的方法