单例模式
单例模式的核心是保证一个类只有一个实例,并且提供一个访问实例的全局访问点。
单例的使用场景
- Spring中bean对象的模式实现方式
- servlet中每个servlet的实例
- spring mvc和struts1框架中,控制器对象是单例模式
- 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加
- 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统
- 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次new一个对象去读取。
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源
单例模式优点
- 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决
- 单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理
单例的实现方式
实现方式 | 优缺点 |
---|---|
饿汉模式 | 线程安全,调用效率高 ,但是不能延迟加载 |
懒汉模式 | 线程安全,调用效率不高,能延迟加载 |
双重检测锁式 | 在懒汉式的基础上解决并发问题 |
静态内部类式 | 线程安全,资源利用率高,可以延时加载 |
枚举单例 | 线程安全,调用效率高,但是不能延迟加载 |
饿汉式
/**
* 单例模式:饿汉式
* @author 波波烤鸭
*
*/
public class SingletonInstance1 {
// 声明此类型的变量,并实例化,当该类被加载的时候就完成了实例化并保存在了内存中
private static SingletonInstance1 instance = new SingletonInstance1();
// 私有化所有的构造方法,防止直接通过new关键字实例化
private SingletonInstance1(){}
// 对外提供一个获取实例的静态方法
public static SingletonInstance1 getInstance(){
return instance;
}
}
懒汉式
/**
* 单例模式:懒汉式
* @author 波波烤鸭
*
*/
public class SingletonInstance2 {
// 声明此类型的变量,但没有实例化
private static SingletonInstance2 instance = null;
// 私有化所有的构造方法,防止直接通过new关键字实例化
private SingletonInstance2(){}
// 对外提供一个获取实例的静态方法,为了数据安全添加synchronized关键字
public static synchronized SingletonInstance2 getInstance(){
if(instance == null){
// 当instance不为空的时候才实例化
instance = new SingletonInstance2();
}
return instance;
}
}
此种方式在类加载后如果我们一直没有调用getInstance方法,那么就不会实例化对象。实现了延迟加载,但是因为在方法上添加了synchronized关键字,每次调用getInstance方法都会同步,所以对性能的影响比较大。
双重检测锁式
/**
* 单例模式:懒汉式
* 双重检测机制
* @author 波波烤鸭
*
*/
public class SingletonInstance3 {
// 声明此类型的变量,但没有实例化
private static volatile SingletonInstance3 instance = null;
// 私有化所有的构造方法,防止直接通过new关键字实例化
private SingletonInstance3(){}
// 对外提供一个获取实例的静态方法,
public static SingletonInstance3 getInstance(){
if(instance == null){
synchronized(SingletonInstance3.class){
if(instance == null){
instance = new SingletonInstance3();
}
}
}
return instance;
}
}
不加volatile有指令重排序的问题。添加后可以解决。
静态内部类式
/**
* 静态内部类实现方式
* @author 波波烤鸭
*
*/
public class SingletonInstance4 {
// 静态内部类
public static class SingletonClassInstance{
// 声明外部类型的静态常量
public static final SingletonInstance4 instance = new SingletonInstance4();
}
// 私有化构造方法
private SingletonInstance4(){}
// 对外提供的唯一获取实例的方法
public static SingletonInstance4 getInstance(){
return SingletonClassInstance.instance;
}
}
注意点:
1.外部类没有static属性,则不会像饿汉式那样立即加载对象。
2.只有真正调用getInstance(),才会加载静态内部类。加载类时是线程 安全的instance是static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性.
3. 兼备了并发高效调用和延迟加载的优势!– 兼备了并发高效调用和延迟加载的优势!
枚举单例
/**
* 单例模式:枚举方式实现
* @author dengp
*
*/
public enum SingletonInstance5 {
// 定义一个枚举元素,则这个元素就代表了SingletonInstance5的实例
INSTANCE;
public void singletonOperation(){
// 功能处理
}
}
测试代码
public static void main(String[] args) {
SingletonInstance5 s1 = SingletonInstance5.INSTANCE;
SingletonInstance5 s2 = SingletonInstance5.INSTANCE;
System.out.println(s1 == s2); // 输出的是 true
}
优点:
实现简单
枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞!
缺点:
无延迟加载
单例模式漏洞
- 通过反射的方式我们依然可用获取多个实例(除了枚举的方式)
public static void main(String[] args) throws Exception, IllegalAccessException {
SingletonInstance1 s1 = SingletonInstance1.getInstance();
// 反射方式获取实例
Class c1 = SingletonInstance1.class;
Constructor constructor = c1.getDeclaredConstructor(null);
constructor.setAccessible(true);
SingletonInstance1 s2 = (SingletonInstance1)constructor.newInstance(null);
System.out.println(s1);
System.out.println(s2);
}
输出结果为:
产生了两个对象,和单例的设计初衷违背了。
解决的方式是在无参构造方法中手动抛出异常控制
// 私有化所有的构造方法,防止直接通过new关键字实例化
private SingletonInstance2(){
if(instance != null){
// 只能有一个实例存在,如果再次调用该构造方法就抛出异常,防止反射方式实例化
throw new RuntimeException("单例模式只能创建一个对象");
}
}
- 通过反序列化的方式也可以破解上面几种方式(除了枚举的方式)
public static void main(String[] args) throws Exception, IllegalAccessException {
SingletonInstance2 s1 = SingletonInstance2.getInstance();
// 将实例对象序列化到文件中
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("c:/tools/a.txt"));
oos.writeObject(s1);
oos.flush();
oos.close();
// 将实例从文件中反序列化出来
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("c:/tools/a.txt"));
SingletonInstance2 s2 = (SingletonInstance2) ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
}
输出结果:
是两个不同的对象,同样破坏了单例模式,这种情况怎么解决呢
我们只需要在单例类中重写readResolve方法并在该方法中返回单例对象即可,如下:
package com.dpb.single;
import java.io.ObjectStreamException;
import java.io.Serializable;
/**
* 单例模式:懒汉式
* @author 波波烤鸭
*
*/
public class SingletonInstance2 implements Serializable{
// 声明此类型的变量,但没有实例化
private static SingletonInstance2 instance = null;
// 私有化所有的构造方法,防止直接通过new关键字实例化
private SingletonInstance2(){
if(instance != null){
// 只能有一个实例存在,如果再次调用该构造方法就抛出异常,防止反射方式实例化
throw new RuntimeException("单例模式只能创建一个对象");
}
}
// 对外提供一个获取实例的静态方法,为了数据安全添加synchronized关键字
public static synchronized SingletonInstance2 getInstance(){
if(instance == null){
// 当instance不为空的时候才实例化
instance = new SingletonInstance2();
}
return instance;
}
// 重写该方法,防止序列化和反序列化获取实例
private Object readResolve() throws ObjectStreamException{
return instance;
}
}
说明:readResolve方法是基于回调的,反序列化时,如果定义了readResolve()则直接返回此方法指定的对象,而不需要在创建新的对象!
总结:
这几种设计模式如何选择
1、单例对象占用资源少,不需要延时加载:
枚举式 好于 饿汉式
2、单例对象占用资源大,需要延时加载:
静态内部类式 好于 懒汉式
标签:instance,实例,static,单例,new,设计模式,public From: https://www.cnblogs.com/ytmm/p/16712327.html