一、单例模式作用:
保证一个类只有一个实例,提供一个获取该实例的全局方法。
二、分类:
饿汉式、懒汉式、双重检测锁、枚举、静态内部类
三、选用:
需要延时加载时:静态内部类 优于 懒汉式
不需要延时加载:枚举 优于 饿汉式
饿汉式:
/**
* eager:
* advantage: 1.thread security 2.high call efficiency
* disadvantage: 2.not a lazy loading
* 注意:
* static修饰的变量,在类装载时,就被初始化。
* 虚拟机保证只会载一次该类。
* 所以不会发生并发访问的问题,是线程安全的。
*/
public class Eager {
private static Eager eager = new Eager();
private Eager(){}
public static Eager getInstance(){
return eager;
}
}
懒汉式:
/**
* lazy:
* 特点:1.线程安全。2.调用效率低。3.懒加载
* 注意:synchronized一定要有,否则在并发访问时,可能出现多个实例的情况。
*/
public class Lazy {
private static Lazy lazy;
private Lazy(){}
public static synchronized Lazy getInstance() {
if (lazy == null) {
lazy = new Lazy();
}
return lazy;
}
}
(双重检测锁:由于编译优化和JVM底层内部模型的原因,可能会出现问题)
静态内部类:
/**
* 静态内部类:
* 特点:1、线程安全。2、调用效率高。3、懒加载
* 注意:只有真正调用getInstance()时,才会加载静态内部类。加载类时是线程安全的。
*/
public class StaticInner {
private static class si{
private static StaticInner instance = new StaticInner();
}
private StaticInner(){}
public static StaticInner getInstance(){
return si.instance;
}
}
枚举:
/**
* 枚举:
* 特点:1、线程安全。2、调用效率高。3、不能懒加载
* 注意:枚举本身就是单例模式。有JVM从根本上提供保障。可以直接避免反射和反序列化的漏洞。
*/
public enum EnumSingleton {
INSTANCE;
// 添加自己需要的方法,去操作单例对象。
public void operation(){
}
}
四、测试
反射破解单例
反序列化破解单例
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class TestSingleton {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
System.out.println(Eager.getInstance() == Eager.getInstance());
System.out.println(EnumSingleton.INSTANCE == EnumSingleton.INSTANCE);
//反射破解单例
Class<Eager> aClass = (Class<Eager>) Class.forName("com.hui.Eager");
Constructor<Eager> c = aClass.getDeclaredConstructor(null);
c.setAccessible(true);
System.out.println(c.newInstance() == c.newInstance());
//反序列化破解单例
EagerAdvanced ea = EagerAdvanced.getInstance();
FileOutputStream fos = new FileOutputStream("d:/a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(ea);
oos.close();
fos.close();
FileInputStream fis = new FileInputStream("d:/a.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
EagerAdvanced ea1 = (EagerAdvanced) ois.readObject();
System.out.println(ea == ea1);
ois.close();
fis.close();
}
}
五、饿汉式(反射、反序列化)
/**
* eager:
* 特点:1.线程安全。2调用效率高。3.不能懒加载
* 注意:
* static修饰的变量,在类装载时,就被初始化。
* 虚拟机保证只会载一次该类。
* 所以不会发生并发访问的问题,是线程安全的。
*/
public class EagerAdvanced implements Serializable {
private static EagerAdvanced eager = new EagerAdvanced();
private EagerAdvanced(){
if (eager != null) {
throw new RuntimeException(); //避免反射破解单例
}
}
public static EagerAdvanced getInstance(){
return eager;
}
/**
* 当一个类中有readResolve()时,再发生反序列化的情况下。
* 会调用readResolve()直接返回实例,不会再重新生成对象。
* @return
* @throws ObjectStreamException
*/
private Object readResolve() throws ObjectStreamException {
return eager; //避免反序列化破解单例
}
}
六、测试多线程环境下,几种单例模式的效率
public class TestEfficiency {
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
int threadNum = 10;
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 < 100000; i++){
Object instance = Lazy.getInstance();
}
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();
long end = System.currentTimeMillis();
System.out.println("总耗时:" + (end - start));
}
}