单例模式
实现方式
饿汉式
饿汉式(线程安全,调用效率高。 但是不能延迟加载)
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
懒汉式会比其他方式高一到两个数量级