单例模式

实现方式

饿汉式

饿汉式(线程安全,调用效率高。 但是不能延迟加载)

package Singleton;

/**
 * 测试饿汉式单例模式
 * @author 
 *
 */
public class Demo01 {
	//类初始化时,立即加载加载这个对象(没有延时加载的优势); 加载类时,天然是线程安全的
	private static Demo01 instance = new Demo01();
	
	private Demo01() {
	}

	public static Demo01 getInstance() {
		return instance;
	}
}

懒汉式

懒汉式(线程安全,调用效率不高。但是可以延时加载)

package Singleton;

/**
 * 测试懒汉式单例模式
 * @author 
 *
 */
public class Demo02 {
	//类初始化时,不初始化这个对象(延时加载,真正用的时候在加载); 
	private static Demo02 instance;
	
	private Demo02() {//私有化构造器
	}

	public static synchronized Demo02 getInstance() {
		if(instance == null) {
			instance = new Demo02();
		}
		return instance;
	}
}

双层检测锁式

(由于JVM底层内部模型原因,偶尔会出问题,不建议使用)

package Singleton;

/**
 * 双重检测锁实现单例模式
 * 	提高了执行的效率不必每次获取对象时都进行同步,只有第一次才同步,创建了以后就没有必要了
 * 
 * 问题:
 * 	由于编译器优化问题和JVM底层内部模型原因,偶尔会出现问题,不建议使用
 * @author 
 *
 */
public class Demo03 {
	private static Demo03 instance = null;
	
	private Demo03() {
	}

	public static Demo03 getInstance() {
		if(instance == null) {
			Demo03 sc;
			synchronized (Demo03.class) {
				sc = instance;
				if(sc == instance) {
					synchronized (Demo03.class) {
						if (sc == null) {
							sc = new Demo03();
						}
					}
					instance = sc;
				}
			}
		}
		return instance;
	}
}

静态内部类式

(线程安全,调用效率高。并且可以延时加载)

package Singleton;

/**
 * 静态内部类的实现单例模式
 * 	线程安全、调用效率高、并且使用了延时加载
 * 
 * 要点:
 * 	-外部没有static属性,不会像饿汉式那样立即加载对象
 * 	-只有真正调用getInstance()才会加载静态内部类。加载类时是线程安全的。	
 * 	 instance是static final类型,确保了内存中只有一个实例存在,而且
 * 	   只能被赋值一次,从而保证了线程安全性、
 * 	-兼备了并发高效调用和延迟加载的优势
 * 
 * @author 
 *
 */
public class Demo04 {
	
	private static class classInstance {
		private static final Demo04 instance = new Demo04();
	}
	
	public static Demo04 getInstance() {
		return classInstance.instance;
	}
	
	private Demo04() {
	}
	
}

枚举单例

(线程安全,调用效率高,不能延时加载)可以天然的防止反射和反序列化漏洞

package Singleton;

/**
 * 枚举实现单例模式
 * 优点:	
 * 	实现简单
 * 	枚举本身就是单例模式。由JVM从根本上提供保障,可以避免通过反射和反序列化的漏洞去创建新的对象
 * 	调用效率高
 * 
 * 缺点:
 * 	无延迟加载(懒加载)
 * @author ly
 *
 */
public enum Demo05 {

	//这个枚举元素、本身就是单例对象
	INSTANCE;
	
	//添加自己需要的操作
	public void singletOperation() {
		
	}
}

如何选用

单例模式 占用资源少,不需要延时加载:枚举式 好于 饿汉式

单例模式 占用资源多,需要延时加载:静态内部类 好于 懒汉式

存在问题

-反射可以破解上面几种实现方式(不包含枚举) (可以在构造方法中手动抛出异常控制)
-反序列化可以破解上面几种实现方式(不包含枚举)

通过反射的方式直接调用私有构造器

import java.lang.reflect.Constructor;

/**
 * 测试反射破解单例模式
 * @author      
 *
 */
public class Client2 {
	public static void main(String[] args) throws Exception {
		Demo02 d1 = Demo02.getInstance();
		Demo02 d2 = Demo02.getInstance();
		
		System.out.println(d1);
		System.out.println(d2);

		//通过反射的方式直接调用私有构造器
		Class<Demo02> clazz = (Class<Demo02>) Class.forName("Singleton.Demo02");
		Constructor<?> c = clazz.getDeclaredConstructor(null);
		c.setAccessible(true);//跳过权限检查 可以访问私有成员
		Demo02 d3 = (Demo02) c.newInstance();
		Demo02 d4 = (Demo02) c.newInstance();
		
		System.out.println(d3);
		System.out.println(d4);
			}
}

通过反序列化的方式构造多个对象

注意:文件位置不建议放在系统盘会出现权限问题(e:/a.txt)

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * 测试反序列化破解单例模式
 * @author 
 *
 */
public class Client2 {
	public static void main(String[] args) throws Exception {
		Demo02 d1 = Demo02.getInstance();
		Demo02 d2 = Demo02.getInstance();
		
		System.out.println(d1);
		System.out.println(d2);

		//通过反序列化的方式构造多个对象
		FileOutputStream fos = new FileOutputStream("e:/a.txt");
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		oos.writeObject(d1);
		oos.close();
		fos.close();
		
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("e:/a.txt"));
		Demo02 d3 = (Demo02) ois.readObject();
		System.out.println(d3);
			}
}

测试懒汉单例模式防止反射和反序列化漏洞

package Singleton;

import java.io.Serializable;

/**
 * 测试懒汉式单例模式(如何防止反射和反序列化漏洞)
 * @author 
 *
 */
public class Demo06 implements Serializable {
	//类初始化时,不初始化这个对象(延时加载,真正用的时候在加载); 
	private static Demo06 instance;
	
	
	private Demo06() {//私有化构造器
		if (instance != null) {
			throw new RuntimeException();//在构造方法中手动抛出异常来防反射
		}
	}

	public static synchronized Demo06 getInstance() {
		if(instance == null) {
			instance = new Demo06();
		}
		return instance;
	}
	
	//在反序列化时。如果定义了readResolve()方法  则直接返回此方法指定对象。而不需要在单独去创建对象
	private Object readResolve() {
		return instance;
	}
}

测试多线程环境下五种创建单例模式方法的效率

package Singleton;

import java.util.concurrent.CountDownLatch;

/**
 * 测试多线程环境下5种创建单例模式的方法的效率
 * @author 
 *
 */
public class Client3 {
	public static void main(String[] args) throws Exception {
		//饿汉式
		long start = System.currentTimeMillis();
		int threadNum = 10;
		final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
		
		for(int i=0; i<10; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					for(int i=0; i<100000; i++) {
						Object o = Demo01.getInstance();
//						Object o = Demo05.INSTANCE;//枚举式
					}
					countDownLatch.countDown();
				}
			}).start();
		}
		
		countDownLatch.await();//main线程阻塞 直至计数器变为0,才会继续往下执行
		
		long end = System.currentTimeMillis();
		System.out.println("总耗时:"+(end-start));
			
	}
}

仅供参考:
饿汉式:43
懒汉式:105
双重检测锁:52
静态内部类:48
枚举:37
懒汉式会比其他方式高一到两个数量级

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值