设计模式-创建型-单例模式
单例模式
单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。
单例模式是创建型模式,Spring框架中的ApplicationContext、J2EE中的ServletContext和ServletContextConfig、数据库的连接池都是单例模式
1、饿汉单例模式
饿汉单例模式在类加载的时候就立即初始化,并且创建单例对象。
绝对的线程安全,在线程还没有出现以前就实例化了,不可能存在访问安全问题。
-
优点:没有任何加锁操作、执行效率比较高,用户体验比懒汉单例模式更好
-
缺点:类加载的时候就初始化,不管用不用都占空间,浪费内存
1.1、饿汉单例案例
经典案例:
/**
* TODO 懒汉单例模式
* 饿汉单例模式在类加载的时候就立即初始化,并且创建单利对象
* 绝对线程安全
* @author ss_419
* @version 1.0
* @date 2023/9/1 09:10
*/
public class HungrySingleton {
// 先静态,后动态
// 先属性,后方法
// 先上后下
private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();
private HungrySingleton() {}
// 通过getInstance返回对象实例
public static HungrySingleton getInstance(){
return HUNGRY_SINGLETON;
}
}
改进写法,使用静态代码块机制:
/**
* TODO 静态饿汉单例模式
* 利用静态代码块的机制
* 饿汉单例模式适用于单例对象较少的情况
* @author ss_419
* @version 1.0
* @date 2023/9/1 09:16
*/
public class HungryStaticSingleton {
private static final HungryStaticSingleton INSTANCE ;
/**
* 通过静态代码块对类进行实例化
*/
static {
// 静态代码块,在初始化之前的连接阶段中的准备阶段就对静态变量分配内存、设置初始值等操作
INSTANCE = new HungryStaticSingleton();
}
/**
* 不能通过构造器对类进行实例化操作
*/
private HungryStaticSingleton() {
}
// 提供一个全局访问点,以供实例化对象
public static HungryStaticSingleton getInstance() {
return INSTANCE;
}
}
2、懒汉单例模式
特点:被外部类调用的时候内部类才会加载
2.1、懒汉单例案例
懒汉单例模式在外部需要使用的时候才进行实例化:
/**
* TODO 懒汉单例模式
* 懒汉单例模式:在外部需要使用的时候才进行实例化
*
* @author ss_419
* @version 1.0
* @date 2023/9/1 09:20
*/
public class LazySimpleSingleton {
private LazySimpleSingleton() {
}
// 静态块,公共内存区域
private static LazySimpleSingleton lazy = null;
// 提供全局访问点,实例化单例对象
public static LazySimpleSingleton getInstance() {
if (lazy == null){
// 对象没有创建的时候才new,否则就返回之前所存在的对象
return new LazySimpleSingleton();
}
return lazy;
}
}
创建一个线程类ExectorThread:
public class ExectorThread implements Runnable{
@Override
public void run() {
// 通过懒汉单例中提供的全局访问点创建一个单例对象
LazySimpleSingleton singleton = LazySimpleSingleton.getInstance();
// 查看线程中存在的对象
System.out.println(Thread.currentThread().getName() + ": " + singleton);
}
}
测试代码:
@Test
public void test1(){
Thread t1 = new Thread(new ExectorThread());
Thread t2 = new Thread(new ExectorThread());
// 这里测试,出现两个类不同的情况,单例存在线程安全 问题
t1.start();
t2.start();
System.out.println("End");
}
通过测试,上面的代码存在线程安全问题,怎么样才能使线程安全呢?
第一个方法:通过对获得实例方法进行加锁操作,保证当前线程获得实例时,其他线程都处于阻塞状态
package org.pp.my.design_pattern.create.singleton2.lazy;
/**
* TODO 懒汉单例模式
* 懒汉单例模式:在外部需要使用的时候才进行实例化
*
* @author ss_419
* @version 1.0
* @date 2023/9/1 09:20
*/
public class LazySimpleSingleton {
private LazySimpleSingleton() {
}
// 静态块,公共内存区域
private static LazySimpleSingleton lazy = null;
/**
* 为了保证懒汉单例模式在多线程环境下线程安全,通过synchronized加锁实现
* @return
*/
public synchronized static LazySimpleSingleton getInstance() {
if (lazy == null){
// 对象没有创建的时候才new,否则就返回之前所存在的对象
return new LazySimpleSingleton();
}
return lazy;
}
}
但是通过synchronized加锁的方式,会使性能降低
使用双重锁既能兼顾线程安全又能提升程序性能:
/**
* TODO 懒汉模式双重检查锁
*
* @author ss_419
* @version 1.0
* @date 2023/9/1 09:34
*/
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton lazy = null;
private LazyDoubleCheckSingleton(){
}
public static LazyDoubleCheckSingleton getInstance() {
if (lazy == null){
synchronized (LazyDoubleCheckSingleton.class){
lazy = new LazyDoubleCheckSingleton();
// 1、分配内存给这个对象
// 2、初始化对象
// 3、设置lazy指向刚分配的内存地址
}
}
return lazy;
}
}
采用静态内部类方式,避免加锁操作
/**
* TODO 懒汉模式-静态内部类
* 通过静态内部类来避免使用synchronized加锁的情况
* @author ss_419
* @version 1.0
* @date 2023/9/1 09:39
*/
public class LazyInnerClassSingleton {
// 使用LazyInnerClassSingleton,默认会先初始化内部类
// 如果没使用,则内部类是不加载的
private LazyInnerClassSingleton(){
}
// 每一个关键字都不是多余的,static是为了使单例的空间共享,保证这个方法不会被重写、重载
public static final LazyInnerClassSingleton getInstance(){
return LazyHolder.LAZY;
}
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
2.2、反射破坏单例
通过反射机制,获取类的私有构造方法,强制访问
/**
* 通过反射来破坏单例
*/
@Test
public void test2(){
try {
// 反射获取单例类
Class<LazyInnerClassSingleton> clazz = LazyInnerClassSingleton.class;
// 通过反射获取私有的构造方法
Constructor<LazyInnerClassSingleton> c = clazz.getDeclaredConstructor(null);
// 强制访问
c.setAccessible(true);
// 暴力初始化
Object o1 = c.newInstance();
// 调用了两次构造方法,相当于“new”了两次,犯了原则性错误
Object o2 = c.newInstance();
System.out.println("o1 = " + o1);
System.out.println("o2 = " + o2);
System.out.println(o1 == o2);
}catch (Exception e){
}
}
为了避免上述情况发生,在构造器中做一些操作,一旦出现多次重复创建,则直接抛出异常
/**
* TODO 史上最强的懒汉单例模式
* 一旦出现多次重复创建,则直接抛出异常
*
* @author ss_419
* @version 1.0
* @date 2023/9/1 09:51
*/
public class LazyInnerClassNoNullSingleton {
// 使用LazyInnerClassGeneral的时候,默认会先初始化内部类
// 如果没有使用,则内部类是不加载的
private LazyInnerClassNoNullSingleton(){
if (LazyHolder.LAZY != null){
// 不为null,说明对象已经实例化过了
throw new RuntimeException("不允许创建多个实例");
}
}
/**
* 每一个关键字都不是多余的
* static是为了单例的空间共享,保证这个方法不会被重写、重载
* @return
*/
public static LazyInnerClassNoNullSingleton getInstance(){
// 在返回之前,一定会先加载内部类
return LazyHolder.LAZY;
}
/**
* 默认不加载
*/
private static class LazyHolder {
private static final LazyInnerClassNoNullSingleton LAZY = new LazyInnerClassNoNullSingleton();
}
}
posted on 2023-09-01 13:39 JavaCoderPan 阅读(9) 评论(0) 编辑 收藏 举报