设计模式——单例模式详解
目录
- 设计模式类型
- 单例模式
- 单例模式方式
- 饿汉式
- 静态常量方式
- 静态代码块形式
- 懒汉式
- 线程不安全(不推荐)
- 懒汉式优化(不推荐)
- 双重检查(推荐方式)
- 静态内部类(推荐方式)
- 枚举方式(推荐方式)
- 单例模式在JDK中的使用
- 单例模式注意事项和细节说明
- 单例模式的使用场景
设计模式类型
设计模式分为三种类型,共23种
- 创建型模式: 单例模式,抽象工厂模式,原型模式,建造者模式,工厂模式
- 结构性模式: 适配器模式,桥接模式,装饰模式,组合模式,外观模式,享元模式,代理模式
- 行为型模式: 模板方法模式,命令模式,访问者模式,迭代器模式,观察者模式,中介者模式,备忘录模式,解释器模式,状态模式,策略模式,责任链模式
单例模式
所谓类的单例设计模式,就是采取一定的方法保证在整个系统中,对某个类 只能存在一个对象实例,并且该类值提供一个取得对象实例的方法(静态方法)
单例模式方式
饿汉式
静态常量方式
package 单例模式.饿汉式;
/**
* @author Han
* @data 2023/10/27
* @apiNode
*/
public class Test1 {
public static void main(String[] args) {
Obj obj1 = Obj.getObj();
Obj obj2 = Obj.getObj();
// 因为是单例模式所以这两个对象是同一个,所以返回true
System.out.println(obj1 == obj2);
}
}
class Obj {
// 创建一个私有的静态对象
private final static Obj obj = new Obj();
// 将构造方法私有化
private Obj() {
}
// 提供静态公共方法将这个对象返回
public static Obj getObj() {
return obj;
}
}
静态代码块形式
package 单例模式.饿汉式;
/**
* 静态代码块方式
*
* @author Han
* @data 2023/10/27
* @apiNode
*/
public class Test2 {
public static void main(String[] args) {
Obj2 obj21 = Obj2.getObj();
Obj2 obj22 = Obj2.getObj();
// 因为是单例模式所以这两个对象是同一个,所以返回true
System.out.println(obj21 == obj22);
}
}
class Obj2 {
// 声明一个私有的静态对象
private static Obj2 obj2;
static {
// 在静态代码块中创建对象
obj2 = new Obj2();
}
// 将构造方法私有化
private Obj2() {
}
// 提供静态公共方法将这个对象返回
public static Obj2 getObj() {
return obj2;
}
}
优缺点说明:
- 优点:写法简单,在类装载是完成实例化,避免了线程同步问题
- 却爱:在类转载的时候就完成实例化,没有达到lazy loading的效果,如果从始至终都没有用过这个实例,则会造成内存的浪费
- 这种法方式居于classloder机制避免了多线程的同步问题,不过 obj是在类装载是就实例化了,在单例模式中大多都是调用getObj方法
- 结论:这种单例模式可用,可能会造成内存浪费
懒汉式
线程不安全(不推荐)
package 单例模式.懒汉式;
/**
* 这种方式是线程不安全的
* 原因在于在多线程状态下,if判断条件,
* 可以能会出现第一个对象还未创建,第二个线程就去判断
* 而发生创建多个对象的情况
* @author Han
* @data 2023/10/27
* @apiNode
*/
public class Test1 {
public static void main(String[] args) {
Obj obj1 = Obj.getObj();
Obj obj2 = Obj.getObj();
System.out.println(obj1 == obj2);
}
}
class Obj {
// 声明一个静态对象
private static Obj obj;
// 私有化构造函数
private Obj() {}
// 提供获取单例对象的方法
public static Obj getObj(){
// 如果还没有创建对象再去创建,不会发生内存的浪费
if (obj == null) {
obj = new Obj();
}
return obj;
}
}
优缺点说明
- 起到了lazy loading的效果,但是只能在单线程下使用
- 如果在多线程下使用,一个线程进入了
if(obi == null)
判断语句块,还未来的及王往下执行,另一个线程也通过这个判断语句,这是会发生创建多个实例的错误,所以在多线程环境下不能使用 - 结论:在实际开发中,不要使用这种方式
懒汉式优化(不推荐)
优化,加同步方法,解决线程不安全问题 存在效率问题,
package 单例模式.懒汉式;
/**
* 这种方式虽然解决了线程安全问题
* 但是效率很低
* @author Han
* @data 2023/10/27
* @apiNode
*/
public class Test2 {
public static void main(String[] args) {
Obj2 obj1 = Obj2.getObj();
Obj2 obj2 = Obj2.getObj();
System.out.println(obj1 == obj2);
}
}
class Obj2 {
// 声明一个静态对象
private static Obj2 obj;
// 私有化构造函数
private Obj2() {}
// 提供获取单例对象的方法
// 加入了同步处理的代码,解决线程安全问题
public static synchronized Obj2 getObj(){
// 如果还没有创建对象再去创建,不会发生内存的浪费
if (obj == null) {
obj = new Obj2();
}
return obj;
}
}
优缺点说明
- 解决了线程安全问题
- 效率太低,每个线程在获得类的实例的时候,执行getObj方法都要进行同步,但是这个方法只需要执行一次实例化代码就够了,后面想要获取该实例直接return就行了,方法进行同步效率太低
- 结论:在实际开发中,不推荐使用这中方式
双重检查(推荐方式)
package 单例模式.双重检查;
import com.sun.org.apache.xpath.internal.operations.Variable;
/**
* 双重检查
* 解决线程安全问题,并且支持懒加载
*
* @author Han
* @data 2023/10/27
* @apiNode
*/
public class Test1 {
public static void main(String[] args) {
Obj obj1 = Obj.getObj();
Obj obj2 = Obj.getObj();
System.out.println(obj1 == obj2);
}
}
class Obj {
// 声明一个静态对象
// 并且使Obj的对象的改变立即更新到内存,在下面的双重检查中判断是否为nul
private static volatile Obj obj;
// 私有化构造函数
private Obj() {
}
// 提供获取单例对象的方法
public static Obj getObj() {
// 如果还没有创建对象再去创建,不会发生内存的浪费
if (obj == null) {
// 同步代码块
synchronized (Obj.class) {
// 再一次检查是否为null
if (obj == null) {
obj = new Obj();
}
}
}
return obj;
}
}
优缺点说明
- 双重检查概念是多线程开发中常用到的,如代码中所示,我们进行了两次
if (obj == null )
的检查,这样就可以保证线程安全 - 这样,实例化代码也只执行一次,后面再次访问时,判断if 直接return实例化对象,也避免了反复进行方法同步
- 线程安全,延迟加载,效率较高
- 结论:在开发中,推荐使用这种单例设计模式
静态内部类(推荐方式)
package 单例模式.静态内部类;
import com.sun.org.apache.xpath.internal.operations.Variable;
/**
* 静态内部类
*
* @author Han
* @data 2023/10/27
* @apiNode
*/
public class Test1 {
public static void main(String[] args) {
Obj obj1 = Obj.getObj();
Obj obj2 = Obj.getObj();
System.out.println(obj1 == obj2);
}
}
class Obj {
// 私有化构造函数
private Obj() {
}
// 使用静态内部类
public static Obj getObj() {
// 使用静态内部类中属性
// 类加载时是线程安全的
return StaticObj.OBJ;
}
// 静态内部类在类加载时不会马上加载,解决懒加载
// 只有使用到静态内部类中的属性时,静态内部类才会加载
static class StaticObj {
private static final Obj OBJ = new Obj();
}
}
说明
- 这种方式采用了类装载的机制来保证初始化实例时只有一个线程
- 静态内部类方式在Obj类被装载时不会立即实例化,而是在需要实例化时,调用getObj方法时,才会装载StaticObj类,从而完成Obj的实例化
- 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的
- 优点;避免了线程不安全,利用静态内部类特点实现懒加载,效率高,
- 结论:推荐使用
枚举方式(推荐方式)
package 单例模式.枚举方式;
enum Type {
INSTANCE,
USER("小韩", 12, "学生");
String name;
String job;
int i;
Type(String name, int i, String job) {
this.i = i;
this.job = job;
this.name = name;
}
Type() {
}
public void sayOk() {
System.out.println("ok");
}
@Override
public String toString() {
return "Type{" +
"name='" + name + '\'' +
", job='" + job + '\'' +
", i=" + i +
'}';
}
}
/**
* @author Han
* @data 2023/10/28
* @apiNode
*/
public class Test {
public static void main(String[] args) {
Type instance = Type.INSTANCE;
Type instance2 = Type.INSTANCE;
Type user1 = Type.USER;
Type user2 = Type.USER;
System.out.println(Type.INSTANCE);
System.out.println(Type.USER);
System.out.println(instance == instance2); // true
System.out.println(user2 == user1); // true
}
}
优点说明
- 借助了JDK1.5中添加的枚举来实现单例模式,不仅能避免多线程问题,而且还能防止反序列化重新创建新的对象
- 这种方式推荐使用
单例模式在JDK中的使用
单例模式注意事项和细节说明
- 单例模式保障了系统中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
- 当想实例化一个单例对象的使用,必须要记住使用相应的获取对象的方法,而不是使用new
单例模式的使用场景
- 需要频繁的进行创建和销毁对象
- 创建对象是耗时过多或者耗费资源过多,但是又经常使用到的对象,工厂类对象
- 频繁访问数据库或文件的对象(比如数据源,session工厂等)