单例模式
核心作用
单例模式,顾名思义,就是保证一个类只有一个实例,并且提供个公共的访问点进行访问,以获取该实例。
单例模式常用场景
windows系统的任务管理器,开发项目中用于读取配置信息的类,数据库的连接池,servlet中application实例和Spring中bean的默认等等,都是单例模式。
单例模式的优势
单例模式主要的优势就是只创建一个实例,减少了因创建大量的相同实例的开销。
单例模式的实现方式
常见的单例模式的实现就是我们所说的“饿汉式”,“懒汉式”,其中还有三种其它的方式也可以实现单例模式,分别是:“双重锁检测式”、“静态内部类式”和“枚举单例模式”。
饿汉式
一个私有化的构造方法,防止其他类通过构造方法实例化
一个私有的静态对象(类加载时就创建对象,所以称为“饿汉式”)
一个公共的获取该对象的方法,提供已经创建好的对象
/**
* 单例模式(饿汉式)
* @author 90948
*/
public class SingletonDemo1 {
//类加载时候立即初始化实例(没有懒加载的优势)。因为是在类加载时候初始化,天然的线程安全
private static SingletonDemo1 instance = new SingletonDemo1();
private SingletonDemo1(){}
//因为方法不是同步的,所以调用效率高
public static SingletonDemo1 getInstance(){
return instance;
}
}
懒汉式
懒汉式的设计和饿汉式的差不多,只是创建对象的时间是需要用到该对象时候才创建,这么做可以防止因类加载时创建而降低了效率。而且公共的方法需要同步,防止因多线程访问造成创建不同对象。
/**
* 单例模式(懒汉式)
* @author 90948
*/
public class SingletonDemo2 {
//类加载时候不初始化实例,资源利用率高
private static SingletonDemo2 instence;
private SingletonDemo2(){}
//什么时候真正需要用的时候再创建对象,但是方法要同步,并发率低
public static synchronized SingletonDemo2 getInstance(){
if(instence==null){
instence = new SingletonDemo2();
}
return instence;
}
}
下面是三种其它的方式实现“单例模式”
双重锁检测式
双重锁检测主要做法是在公共方法的内部进行同步。在JDK1.5版本之前,由于编译器的优化和底层模型的实现,无法使用双锁检测来实现单例模式。在JDK1.5之后修复了该问题。
/**
* 双重检测锁模式
* @author 90948
*/
public class SingletonDemo3 {
private volatile static SingletonDemo3 instance;
private SingletonDemo3(){}
public static SingletonDemo3 getInstance(){
if(instance==null){
synchronized (SingletonDemo3.class) {
if(instance==null){
instance = new SingletonDemo3();
}
}
}
return instance;
}
}
静态内部类实现
静态内部类也可以做到线程安全,调用率高,延迟加载等优势。
/**
* 静态内部类方式
* 线程安全,调用率高,延迟加载
* @author 90948
*/
public class SingletonDemo4 {
private static class SingletonInnerClass{
private static final SingletonDemo4 instance = new SingletonDemo4();
}
private SingletonDemo4(){}
public static SingletonDemo4 getInstance(){
return SingletonInnerClass.instance;
}
}
采用枚举方式实现单例模式
枚举类天然的线程安全,代码量少,但是没有延迟加载特点。
/**
* 枚举方式单例模式
* 线程安全,调用率高,没有延迟加载
* @author 90948
*/
public enum SingletonDemo5 {
INSTANCE;
public void singletonOperation(){
//可以添加别的操作,调用时候直接用SingletonDemo5.INSTANCE
}
}
单例模式的破解与防止破解
单例模式只创建一个实例,但是采用Java的反射机制和序列还反序列化方式可以造成单例模式创建不同的对象(枚举方式除外),以“懒汉式”为例:
1. 采用反射方式破解
//懒汉式单例模式
import java.io.ObjectStreamException;
/**
* 单例模式(懒汉式)
* @author 90948
*/
public class SingletonDemo2 {
//类加载时候不初始化实例,资源利用率高
private static SingletonDemo2 instence;
private SingletonDemo2(){
}
//什么时候真正需要用的时候再创建对象,但是方法要同步,并发率低
public static synchronized SingletonDemo2 getInstance(){
if(instence==null){
instence = new SingletonDemo2();
}
return instence;
}
}
//创建测试类
import java.lang.reflect.Constructor;
public class test {
public static void main(String[] args) throws Exception {
Class<SingletonDemo2> clazz = (Class<SingletonDemo2>) Class.forName("sunshuo.pattern.singleton.SingletonDemo2");
Constructor<SingletonDemo2> constructor = clazz.getDeclaredConstructor(null);
constructor.setAccessible(true);
SingletonDemo2 s1 = constructor.newInstance();
SingletonDemo2 s2 = constructor.newInstance();
System.out.println(s1);
System.out.println(s2);
System.out.println(s2==s1);
}
}
//结果:
//sunshuo.pattern.singleton.SingletonDemo2@2a139a55
//sunshuo.pattern.singleton.SingletonDemo2@15db9742
//false
从结果可以看到创建的对象不唯一,单例模式被破解。
解决方法:
import java.io.ObjectStreamException;
/**
* 单例模式(懒汉式)
* @author 90948
* 在构造方法中添加if条件判断语句,如果条件为真,抛出异常
*/
public class SingletonDemo2 {
//类加载时候不初始化实例,资源利用率高
private static SingletonDemo2 instence;
private SingletonDemo2(){
//防止因暴力反射而破解当你模式
if(instence!=null){
throw new RuntimeException();
}
}
//什么时候真正需要用的时候再创建对象,但是方法要同步,并发率低
public static synchronized SingletonDemo2 getInstance(){
if(instence==null){
instence = new SingletonDemo2();
}
return instence;
}
}
- 采用反序列化方式破解
import java.io.Serializable;
/**
* 单例模式(懒汉式)
* @author 90948
*/
public class SingletonDemo2 implements Serializable{
//类加载时候不初始化实例,资源利用率高
private static SingletonDemo2 instence;
private SingletonDemo2(){
//防止因暴力反射而破解当你模式
if(instence!=null){
throw new RuntimeException();
}
}
//什么时候真正需要用的时候再创建对象,但是方法要同步,并发率低
public static synchronized SingletonDemo2 getInstance(){
if(instence==null){
instence = new SingletonDemo2();
}
return instence;
}
}
package sunshuo.pattern.singleton;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class test {
public static void main(String[] args) throws Exception {
//创建两个对象
SingletonDemo2 s1 = SingletonDemo2.getInstance();
SingletonDemo2 s2 = SingletonDemo2.getInstance();
System.out.println(s1==s2);
//序列化s1对象
FileOutputStream fos = new FileOutputStream("g:/a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s1);
oos.close();fos.close();
//反序列化
FileInputStream fis = new FileInputStream("g:/a.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
SingletonDemo2 s3 = (SingletonDemo2) ois.readObject();
ois.close();
fis.close();
System.out.println(s3==s1);
}
}
//结果:
/*true
*false
*/
解决方法:
import java.io.ObjectStreamException;
import java.io.Serializable;
/**
* 单例模式(懒汉式)
* @author 90948
* 添加readResovle方法
*/
public class SingletonDemo2 implements Serializable{
//类加载时候不初始化实例,资源利用率高
private static SingletonDemo2 instence;
private SingletonDemo2(){
/*//防止因暴力反射而破解当你模式
if(instence!=null){
throw new RuntimeException();
}*/
}
//什么时候真正需要用的时候再创建对象,但是方法要同步,并发率低
public static synchronized SingletonDemo2 getInstance(){
if(instence==null){
instence = new SingletonDemo2();
}
return instence;
}
//防止反序列化创建新的对象,在反序列化时调用该方法返回同一对象
private Object readResolve() throws ObjectStreamException{
return instence;
}
}