单列模式
一、概念
1.单列模式的概念:
单例模式是一种常见的设计模式,它确保一个类只有一个实例存在,并提供一个全局访问点来获取该实例。
主要目的是控制某些类的实例化过程,以保证在整个应用程序中只有一个该类的实例被创建和使用。
2.组成
单列模式 = 饿汉式 + 懒汉式
二、饿汉式
1.概念:
类加载就会导致该单实例对象被创建
2.优点:
(1)线程安全,由于在类加载时就创建了实例,不存在并发创建实例的问题,因此天然是线程安全的。在多线程环境下,无需额外的同步机制就能保证只有一个实例被创建。
(2)在单例对象被加载时就初始化,不会造成加载速度慢的问题。
(3)没有加锁机制,执行效率高
3.缺点:
类加载创建对象 不管用不用该对象,都已经开辟好的内存就会闲置,造成系统资源的浪费。
4.代码实现:
(1)方式一:
package com.wt.singlemode;
/**
* 单列模式 -- 饿汉式
* 实现方式 静态变量
* 饿汉式:类加载就会导致该单实例对象被创建
*/
public class Animal {
//私有构造方法 外界无法创建该类对象 ---- 反射除外 还有其它方法应对反射破坏单列的解决办法 TODO
private Animal(){}
//静态变量创建对象
private static Animal animal= new Animal();
//对外提供方法获取对象
public static Animal getAnimal(){
return animal;
}
}
(2)方式二:
package com.wt.singlemode;
/**
* 单列模式 饿汉式
* 实现方式 静态代码块
*/
public class AnimalByStatic {
private AnimalByStatic() {
}
private static AnimalByStatic animalByStatic;
/**
* 初始化静态变量:静态代码块可以用于初始化类中的静态变量。在类加载时,静态代码块会对静态变量进行赋值操作,确保它们在程序运行前被正确地初始化。
* 执行一次性初始化操作:静态代码块在类加载时只执行一次,因此非常适合执行一些只需要进行一次的初始化操作,如读取配置文件、建立数据库连接等。
* 预加载资源:通过静态代码块,可以在类加载时预加载一些资源,如常用的数据,以提高程序的性能并减少后续操作的延迟。
*/
static {
animalByStatic = new AnimalByStatic();
}
public static AnimalByStatic getAnimalByStatic(){
return animalByStatic;
}
}
三、懒汉式
1.概念:
类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
2.优点:
延迟初始化:第一次使用对象时才创建这个对象,从而节省了内存。
因为没有在类被加载时就进行实例化,所以它可以避免在系统启动时就占用系统资源。
3.缺点:
线程不安全:原始的懒汉式实现通常不是线程安全的。在多线程环境下,如果多个线程同时访问 getInstance
方法,并且此时实例尚未创建,可能会导致创建多个实例,从而破坏单例模式的唯一性。
性能开销:为了解决线程安全问题,通常需要添加同步机制(如 synchronized
关键字),这可能会带来一定的性能开销,尤其是在高并发场景下。
4.代码实现:
(1)方式一:
package com.wt.lazystyle;
/**
* 单列模式 -- 懒汉式
* 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
* 缺点 线程不安全
*/
public class Car {
private Car() {
System.out.println(Thread.currentThread().getName() + "ok");
}
private static volatile Car car;
//解决线程不安全 加一个同步锁 每次读都需要持锁访问 其余的线程需要等待 性能很低 解决 双重检查方法
// public static synchronized Car getCar() {
// if (car == null) {
// car = new Car();
// }
// return car;
// }
//双重检查方法 但多线程条件下 Car对象有可能出现null异常 解决 在car变量添加volatile关键字
public static Car getCar() {
if (car == null) {
synchronized(Car.class) {
if (car == null) {
car = new Car();
}
}
}
return car;
}
}
(2)方式二(推荐静态内部类实现单列模式的懒汉模式):
package com.wt.lazystyle;
/**
* 静态内部类实现单列模式的懒汉模式
*/
public class CarByStaticInnerClass {
private CarByStaticInnerClass(){}
/**
* 静态内部类 jvm在加载外部类的过程中,是不会加载静态内部类的
* 只有内部类的属性或方法被调用时才会被加载,并初始化其静态属性
* static修饰只能被实例化一次,并严格保证实例化顺序
*/
private static class InnerByCar{
private static final CarByStaticInnerClass CAR = new CarByStaticInnerClass();
}
public static CarByStaticInnerClass getCar(){
return InnerByCar.CAR;
}
}
四、通过反射机制和反序列化可以破坏单例模式
1.代码演示通过反射机制破坏单列模式:
package com.wt.lazystyle;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
* 通过反射机制破坏单列模式
*/
public class BreakSingleModeToCarByInner {
public static void main(String[] args) throws Exception{
//1.获取class对象
Class carByStaticInnerClassClass = CarByStaticInnerClass.class;
//2.获取该类构造方法
Constructor declaredConstructor = carByStaticInnerClassClass.getDeclaredConstructor();
//3.取消访问权限
declaredConstructor.setAccessible(true);
//4.获取对象
Object object1 = declaredConstructor.newInstance();
Object object2 = declaredConstructor.newInstance();
//判断是否为同一个对象 false:不是(也就代表通过反射破坏了单列模式) ture:是
System.out.println(object1 == object2);//false
}
}
2.代码演示反射机制破坏单列模式的解决方法:
package com.wt.lazystyle;
/**
* 静态内部类实现单列模式的懒汉模式
*/
public class CarByStaticInnerClass {
/**
* 因为静态内部类没有提供该类的变量(CAR常量对象),所以需要创建一个bool类型进行逻辑判断
* false 表示第一次访问,没有创建对象 true 非第一次访问
*/
private static boolean flag = false;
private CarByStaticInnerClass(){
//多线程条件下 得需要同步代码块
/**
* 反射破解单列模式需要添加的代码
*/
synchronized(CarByStaticInnerClass.class){
if (flag){
throw new RuntimeException("不能创建多个对象");
}
flag = true;
}
}
/**
* 静态内部类 jvm在加载外部类的过程中,是不会加载静态内部类的
* 只有内部类的属性或方法被调用时才会被加载
* 并初始化其静态属性
* static修饰只能被实例化一次,并严格保证实例化顺序
*/
private static class InnerByCar{
private static final CarByStaticInnerClass CAR = new CarByStaticInnerClass();
}
public static CarByStaticInnerClass getCar(){
return InnerByCar.CAR;
}
}