1. 实现要点
1.构造私有。
2.以静态方法或者枚举返回实例。
3.确保实例只有一个,尤其是多线程环境。
4.确保反序列时或通过反射获取该对象时不会重新构建对象。
2. 适用场景
需要频繁实例化然后销毁的对象, 创建对象时耗时过多或者耗资源过多,但又经常用到的对象, 有状态的工具类对象,频繁访问数据库或文件的对象。
3. 五种实现
一、饿汉式:
优点:线程安全,调用效率高。
缺点:不能延迟加载。
/**
* 饿汉式单例模式
*/
public class HungerSingleton {
//类初始化时,立即加载这个对象
private static HungerSingleton instance = new HungerSingleton();
//构造方法私有化
private HungerSingleton(){}
//暴露公有方法,获取对象,jvm加载类是线程安全的,因此不需要同步获取对象的方法
public static HungerSingleton getInstance(){
return instance;
}
}
static变量会在类装载时进行初始化,此时不会涉及到多个线程访问该对象的问题。虚拟机只加载一次该类,不会有并发访问的问题。
二、懒汉式:
优点:线程安全,可以延迟加载。
缺点:高并发下调用效率低。
/**
* 懒汉式单例模式
*/
public class LazySingleton {
//类初始化时,不加载该对象,等用的时候才加载
private static LazySingleton instance;
//构造方法私有化
private LazySingleton(){}
//同步方法(synchronized),调用效率较低
public static synchronized LazySingleton getInstance(){
if (null == instance){
instance = new LazySingleton();
}
return instance;
}
}
实现了懒加载,但是每次获取该对象,都要同步,因此并发效率较低。
三、静态内部类式:
优点:线程安全,调用效率高,延迟加载。
缺点:由于使用静态内部类,不方便传入外界参数。
/**
* 静态内部类单例模式
*/
public class InnerClassSingleton {
//静态内部类中创建对象
private static class SingletonHolder{
private static final InnerClassSingleton instance = new InnerClassSingleton();
}
//构造方法私有化
private InnerClassSingleton(){}
//获取该单例
public static InnerClassSingleton getInstance(){
return SingletonHolder.instance;
}
}
外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化instance,故而不占内存。只有真正调用了getInstance()方法,才会加载内部类。加载类时是线程安全的,instance 属性是static final类型的,只能被赋值一次,保证内存中只有一个实例存在,从而保证线程安全。兼备了高效率调用和延迟加载的优点。但是由于在内部类中进行实例化,因此不方便传参。
四、枚举方式:
优点:线程安全,调用效率高。
缺点:不能实现延迟加载。
/**
* 枚举方式实现单例
*/
public enum EnumSingleton {
//该枚举元素即为单例
INSTANCE;
public void singletonOperation(){
//对该单例的相关操作
}
}
枚举本身就是单例模式。有JVM提供保障,可以避免通过反射和反序列化创建多个对象的漏洞。
五、双重检查锁方式:
由于JVM 的即时编译器中存在指令重排序的优化,偶尔会出问题,因此一般不使用该方式。
/**
* 双重检查锁单例模式
*/
public class DoubleCheckLockSingleton {
//类初始化时,不加载该对象,等用的时候才加载
private static DoubleCheckLockSingleton instance = null;
//构造方法私有化
private DoubleCheckLockSingleton(){}
public static DoubleCheckLockSingleton getInstance(){
//检查对象是否存在
if (null == instance){
DoubleCheckLockSingleton dckls;
//不存在进入同步代码块
synchronized (DoubleCheckLockSingleton.class){
dckls = instance;
//二次检查对象是否存在
if (null == dckls){
synchronized (DoubleCheckLockSingleton.class){
dckls = new DoubleCheckLockSingleton();
}
}
instance = dckls;
}
}
return instance;
}
}
该方式将同步内容放到if内部,不必每次创建对象都要同步,只有第一次才同步。提高了执行效率
4.单例模式破解(不包含枚举)和反破解
反射方式
通过反射方式调用私有构造器破解
/**
* 通过反射破解单例模式
*/
public class CrackSingleton {
public static void main(String[] args) throws Exception {
LazySingleton instance1 = LazySingleton.getInstance();
LazySingleton instance2 = LazySingleton.getInstance();
System.out.println(instance1);
System.out.println(instance2);
//通过反射调用私有构造器
Class<LazySingleton> aClass = (Class<LazySingleton>) Class.forName("com.shan.singleton.LazySingleton");
Constructor<LazySingleton> constructor = aClass.getDeclaredConstructor(null);
constructor.setAccessible(true);//设置私有构造器能够访问
LazySingleton instance3 = constructor.newInstance();
LazySingleton instance4 = constructor.newInstance();
System.out.println(instance3);
System.out.println(instance4);
}
}
运行结果:
怎么避免:通过反射多次调用私有构造方法时,抛出异常,到达只创建一个对象的目的。
/**
* 懒汉式单例模式
*/
public class LazySingleton {
//类初始化时,不加载该队形,等用的时候才加载
private static LazySingleton instance;
//构造方法私有化
private LazySingleton(){
if (null != instance){
//通过反射多次调用私有构造方法时,抛出异常
throw new RuntimeException();
}
}
//同步方法(synchronized),调用效率较低
public static synchronized LazySingleton getInstance(){
if (null == instance){
instance = new LazySingleton();
}
return instance;
}
}
反序列化方式
/**
* 反序列化破解单例模式
*/
public class CrackSingleton {
public static void main(String[] args){
LazySingleton instance1 = LazySingleton.getInstance();
LazySingleton instance2 = LazySingleton.getInstance();
System.out.println(instance1);
System.out.println(instance2);
try (
FileOutputStream fileOutputStream = new FileOutputStream("g:/singleton.txt");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
FileInputStream fileInputStream = new FileInputStream("g:/singleton.txt");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)
){
objectOutputStream.writeObject(instance1);
LazySingleton instance3 = (LazySingleton) objectInputStream.readObject();
System.out.println(instance3);
}catch (Exception e){
e.printStackTrace();
}
}
}
运行结果:
怎么避免:在该类内部定义readResolve()方法。
/**
* 懒汉式单例模式
*/
public class LazySingleton implements Serializable{
//类初始化时,不加载该队形,等用的时候才加载
private static LazySingleton instance;
//构造方法私有化
private LazySingleton(){
if (null != instance){
//通过反射多次调用私有构造方法时,抛出异常
throw new RuntimeException();
}
}
//同步方法(synchronized),调用效率较低
public static synchronized LazySingleton getInstance(){
if (null == instance){
instance = new LazySingleton();
}
return instance;
}
//反序列化时,如果定义了readResolve()则直接返回此方法指定的对象,不再创建新的对象。
private Object readResolve()throws ObjectStreamException{
return instance;
}
}
5.多线程环境下的效率测试
需要借助CountDownLatch类,CountDownLatch是一种java.util.concurrent包下一个同步工具类,它允许一个或多个线程等待直到在其他线程中一组操作执行完成。
测试代码:
/**
* 效率测试
*/
public class SpeedTest {
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
//实例化计数器,指定线程数为10
final CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i=0;i<10;i++){
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0;i<100000;i++){
Object instance = HungerSingleton.getInstance();
}
//线程执行完毕计数器减1
countDownLatch.countDown();
}
}).start();
}
//main线程阻塞,直到计数器减为0
countDownLatch.await();
long end = System.currentTimeMillis();
System.out.println("总耗时:"+(end-start));
}
}
结果:
实现方式 | 耗时 |
---|---|
饿汉式 | 15 |
懒汉式 | 72 |
双重检查锁 | 17 |
静态内部类 | 19 |
枚举 | 16 |
7.总结
如何选用:单例对象占用资源小,不需要延迟加载,考虑枚举式和饿汉式,枚举式要更好一些。
单例对象占用资源大大,需要延迟加载,考虑懒汉式和静态内部类式,静态内部类式要好于懒汉式。
双重检查锁模式不推荐使用。