单例简介:
保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
UML图
常见应用场景:
1.全局计数器采用单例模式,不然不好同步。
2.应用程序的日志应用,共享日志文件一直处于打开状态,所以只能有一个实例去操作,否则内容不好追加。
3.数据库连接池的设计也用单例,否则浪费资源。
4.spring中的bean默认都是单例。
5.servlet是单例。
6.spring mvc/ structs1,控制器对象是单例。
7.项目中工具类,一般都用单例,没必要浪费资源。
常见实现方式
1.饿汉模式(线程安全、调用效率高、不能延时加载)
/**
* 单例模式
* 饿汉模式
* */
public class SingletonDemo1 {
//私有静态属性 类初始化的时候就new了对象
private static /*final*/ SingletonDemo1 instance = new SingletonDemo1();
//私有化构造器
private SingletonDemo1(){}
//只能从这里取对象 方法没有同步 调用效率高
public static /*synchronized*/ SingletonDemo1 getInstance(){
return instance;
}
}
static在类装载的时候就初始化了(第一句 new),JVM保证只会new一次SingletonDemo,所以getInstance方法不会有并发问题,得到的都是同一个对象,可以省略synchronized。
2.懒汉模式(线程安全、调用效率不高(getInstance方法用了synchronized同步方法)、可以延时加载)
/*
* 单例模式
* 懒汉模式
* 延迟加载
* */
public class SingletonDemo2 {
//这里不用new 延迟加载 等到有人要用的时候调用getinstance方法时才new
private static SingletonDemo2 s;
//私有化构造器
private SingletonDemo2(){}
//需要加synchronized,同步化该方法,不然多线程的时候可能会new很多个对象
public static synchronized SingletonDemo2 getInstance(){
if(s == null){
s = new SingletonDemo2();
}
return s;
}
}
3.静态内部类模式(线程安全、调用率高、可以延时加载)
/*
* 单例模式
* 静态内部类模式
* 懒加载 线程安全 效率高
* */
public class SingletonDemo3 {
//静态内部类 初始化SingletonDemo3的时候并不会立即初始化这个静态内部类
private static class singletonClassInstance{
//在静态内部类中定义单例对象
private static final SingletonDemo3 instance = new SingletonDemo3();
}
public static SingletonDemo3 getInstance(){
//调用的时候才初始化这个单例类 静态内部类初始化的时候是天然线程安全的,jvm只会初始化一次静态内部类
return singletonClassInstance.instance;
}
//私有化构造器
private SingletonDemo3(){}
4.懒汉模式 -》 双重同步锁单例模式
public class SingletonExample5 {
// 私有构造函数
private SingletonExample5() {
}
// 1、memory = allocate() 分配对象的内存空间
// 2、ctorInstance() 初始化对象
// 3、instance = memory 设置instance指向刚分配的内存
// 单例对象 volatile + 双重检测机制 -> 禁止指令重排
private volatile static SingletonExample5 instance = null;
// 静态的工厂方法
public static SingletonExample5 getInstance() {
if (instance == null) { // 双重检测机制 // B
synchronized (SingletonExample5.class) { // 同步锁
if (instance == null) {
instance = new SingletonExample5(); // A - 3
}
}
}
return instance;
}
}
5.枚举单例(线程安全、调用率高、不能延时加载、不会被反射反序列化生成多个实例)
/*
* 单例模式
*
* 枚举模式
*
* 无延迟加载
* 避免通过反射和反序列化的漏洞创建新的对象
*/
//枚举类天然单例
public enum SingletonDemo4 {
//定义一个枚举元素,它就代表了SingletonDemo4的一个实例
INSTANCE;
//单例可以有自己的操作
public void singletonOperation(){
//功能处理
}
}
避免通过反射和反序列化的漏洞创建新的对象
枚举类天然单例
无延迟加载
通过反射漏洞创建多个对象
import java.lang.reflect.Constructor;
/**
* @author liyijie
* @date 2018年8月13日下午2:41:23
* @email 37024760@qq.com
* @remark 通过反射、反序列化破解单例(枚举除外)
* @version
*/
public class client2 {
public static void main(String[] args) throws Exception {
SingletonDemo3 s1 = SingletonDemo3.getInstance();
SingletonDemo3 s2 = SingletonDemo3.getInstance();
System.out.println(s1);
System.out.println(s2);
//得到class
Class<SingletonDemo6> class6 = (Class<SingletonDemo6>)Class.forName("com.sid.singleton.SingletonDemo6");
//得到构造器
Constructor<SingletonDemo6> c =class6.getDeclaredConstructor(null);
//跳过权限检查,不然不能访问私有方法
c.setAccessible(true);
SingletonDemo6 s3 = c.newInstance();
SingletonDemo6 s4 = c.newInstance();
System.out.println(s3);
System.out.println(s4);
}
}
防止反射漏洞创建多个对象需要修改单例类的私有化构造器,第二次创建对象时报错
import java.io.ObjectStreamException;
import java.io.Serializable;
/*
* 防止反射、反序列化破解单例
* implements Serializable是用来测试反序列化漏洞的,实际写单例不需要实现这个接口
* */
public class SingletonDemo6 implements Serializable{
//这里不用new 延迟加载 等到有人要用的时候调用getinstance方法时才new
private static SingletonDemo6 s;
//私有化构造器
private SingletonDemo6(){
//第二次创建这个对象报错、防止利用反射漏洞
if(s!=null){
throw new RuntimeException();
}
}
//需要加synchronized,同步化该方法,不然多线程的时候可能会new很多个对象
public static synchronized SingletonDemo6 getInstance(){
if(s == null){
s = new SingletonDemo6();
}
return s;
}
//反序列化拿对象时返回这个s对象,防止利用反序列化漏洞创建多个实例
private Object readResolve() throws ObjectStreamException{
return s;
}
}