- 掌握单例模式的应用场景。
- 掌握IDEA环境下的多线程调试方式。
- 掌握保证线程安全的单例模式策略。
- 掌握反射暴力攻击单例解决方案及原理分析。
- 序列化破坏单例的原理及解决方案。
- 掌握常见的单例模式写法。
定义
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。(Ensure a class has only one instance, and provide a global point of access to it.
)
使用场景
- 要求生成唯一序列号的环境;
- 在整个项目中需要一个共享访问点或共享数据,例如一个 Web 页面上的计数 器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确 保是线程安全的;
- 创建一个对象需要消耗的资源过多,如要访问 IO 和数据库等资源;
- 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式 (当然,也可以直接声明为 static 的方式)。
例如: ServletContext、ServletConfig、BeanFactory、ApplicationContext、DBPool
任何情况下只有一个实例,提供全局一个访问点。
DEMO
饿汉式单例
在初始话的时候直接new出来了,不需要在调用的时候去new,这样就避免了线程安全的问题。
优点
- 线程绝对安全。
- 执行效率高。在类加载的时候就初始化了。
缺点
- 浪费类型空间,占用内存。占着空间,浪费资源。
public class Hungry {
private Hungry(){}
private static final Hungry hungry = new Hungry();
public static Hungry getInstance(){
return hungry;
}
}
懒汉式
在需要的时候才会去创建。
优点
- 占用空间小
缺点
- 需要自己解决线程安全问题。
- 特点
在外部类被调用的时候内部类才会被加载内部类一定是要在方法调用之前初始化巧妙地避免了线程安全问题 这种形式兼顾饿汉式的内存浪费,也兼顾synchronized性能问题,完美地屏蔽了这两个缺点。
史上最牛B的单例模式的实现方式
public class LazyOne{
private static LazyOne lazyOne=null;
private LazyOne(){}
public static LazyOne getInstance(){
//线程不安全
if(lazyOne==null){
lazyOne=new LazyOne();
}
return lazyOne;
}
}
加锁优化
public class LazyTwo{
private static LazyTwo lazyTwo=null;
private LazyTwo(){}
public static synchronized LazyTwo getInstance(){
//线程安全,执行效率低
if(lazyTwo==null){
lazyTwo=new LazyTwo();
}
return lazyTwo;
}
}
public class LazyThree{
private static LazyThree lazythree=null;
private LazyThree(){}
public static LazyThree getInstance(){
//线程安全,执行效率可以
if(lazythree==null){
synchronized(LazyThree.class){
if(lazythree==null){
lazythree=new LazyThree();
}
}
}
return lazythree;
}
}
防止单例被反射入侵
public class LazyFour{
private static LazyFour lazyFour=null;
private LazyFour(){
//防止反射入侵单例
if(lazyFour!=null){
throw new RunTimeException("单例被入侵");
}
}
public static LazyTwo getInstance(){
lazyFour=LazyHandle.lazyFour;
}
private static class LazyHandle{
static LazyFour lazyFour=new LazyFour();
}
}
注册式单例
public class ReginsterSingle {
//HashMap 变成ConcurrentHashmap就是线程安全的
private Map<String,Object> registerMap=new HashMap<>();
private ReginsterSingle(){}
public ReginsterSingle getInstance(String name){
if(name!=null){
name = ReginsterSingle.class.getName();
}
if (registerMap.get(name)==null){
registerMap.put(name, new ReginsterSingle());
}
return (ReginsterSingle)registerMap.get(name);
}
}
枚举式
public enum RegiterEnum {
INSTANCE,BLACK,WHITE;
public void getInstance(){}
}
如何防止被反序列化
实现readResolve方法可以防止反序列化。
序列化和反序列化:
- 把对象转换为字节序列的过程称为对象的序列化。
- 把字节序列恢复为对象的过程称为对象的反序列化。
对象的序列化主要有两种用途:
- 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中。
- 在网络上传送对象的字节序列;
public final static Seriable INSTANCE = new Seriable();
private Seriable(){}
public static Seriable getInstance(){
return INSTANCE;
}
private Object readResolve(){
return INSTANCE;
}