java单例模式

Java 单例模式实现

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点。以下是几种常见的Java单例实现方式:

1. 饿汉式(Eager Initialization)


//单例模式(饿汉模式)
//要求Singleton 类只包含一个实例
class Singleton{
    //static 当前成员成为了类属性
    //此处instance 就可以保证在当前Java进程中只有一份(只能确保在当前进程)
    private static Singleton instance=new Singleton();

    public static Singleton getInstance(){
        return instance;
    }

    //将构造方法设为private 防止外部使用,创建新的实例
    private Singleton(){

    }
}
public class Demo12 {
    //此时new 实例会报错,因为构造方法设为私有,不能被外部访问
    //Singleton s= new Singleton();

    Singleton si=Singleton.getInstance();//获取实例方法

}

​特点​​:

  • 类加载时就创建实例

  • 线程安全

  • 可能造成资源浪费(如果实例未被使用)

2. 懒汉式(Lazy Initialization)

//单例模式(懒汉模式)
class SingletonLazy{
    private static SingletonLazy instance=null;
    static Object locker=new Object();
    //相对于饿汉模式,创建对象的时机推迟到第一次使用的时候再创建
    public static SingletonLazy getInstance() {
        synchronized(locker){
            if (instance == null) {
                instance = new SingletonLazy();
            }
        }
        return instance;
    }

    private SingletonLazy(){

    }
}
public class Demo13 {
    SingletonLazy instance=SingletonLazy.getInstance();
}

​特点​​:

  • 延迟初始化

  • 线程不安全(只出现在实例化之前)=>通过synchronized可解决

  • 性能较差(每次获取实例都要同步)

关于线程安全问题,按上面的方法,只要一进入方法就会加锁,但是懒汉模式的线程安全问题只存在于第一次创建实例对象时存在,因此存在效率变低的问题,通过下面的类型可以解决

3. 双重检查锁(Double-Checked Locking)

class SingletonLazy{
    private volatile static SingletonLazy instance=null;
    static Object locker=new Object();
    //相对于饿汉模式,创建对象的时机推迟到第一次使用的时候再创建
    public static SingletonLazy getInstance() {
        //双重加锁
        //这个条件用于判断是否需要加锁
        if(instance==null){
            synchronized(locker){
                //是否需要创建新实例
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }   
        }
        return instance;
    }

    private SingletonLazy(){

    }
}
public class Demo13 {
    SingletonLazy instance=SingletonLazy.getInstance();
}

​特点​​:

  • 延迟初始化

  • 线程安全

  • 高性能(只有第一次创建时需要同步)

  • 需要volatile关键字修饰实例对象防止指令重排序

为什么只有第一次需要加锁

第一次创建之后, 实例已存在,直接返回,避免不必要的锁开销

volatile修饰的instance变量确保

禁止指令重排序(防止返回未完全初始化的对象)

写操作(创建实例)对其他线程立即可见

如果没有volatile,instance = new SingletonLazy()可能会被重排序为:

  1.  

    分配内存空间

  2.  

    将引用赋值给instance(此时instance!=null)

  3.  

    初始化对象

这样其他线程可能拿到未完全初始化的实例。

import com.mysql.cj.jdbc.MysqlDataSource;

import javax.sql.DataSource;

class DBUtil{
    private volatile static DataSource dataSource=null;
    public static DataSource getDataSource(){
        if(dataSource==null){
            synchronized(DBUtil.class){
                if(dataSource==null){
                    //这种写法有问题
                    //因为锁不禁止重排序,而 volatile不保证多步操作的原子性。
                    dataSource=new MysqlDataSource();
                    ((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1: 3306/hhh?   characterEncoding=utf8&useSSL=false");
                    ((MysqlDataSource)dataSource).setUser("root2");
                    ((MysqlDataSource)dataSource).setPassword("27838");
                }
            }
        }
        return dataSource;
    }
}

​场景:多步非原子操作(如 newsetUrlsetUsersetPassword)​

volatile DataSource dataSource; // 线程A

dataSource = new MysqlDataSource(); // (1)

((MysqlDataSource) dataSource).setUrl(...); // (2)

((MysqlDataSource) dataSource).setUser(...); // (3)

((MysqlDataSource) dataSource).setPassword(...); // (4)

❌ ​​问题​​:

  •  

    volatile​只保证 dataSource引用的写入是原子的​​(即 (1)的赋值)。

  •  

    但 (2)(3)(4)是​​独立的操作​​,volatile​不保证它们作为一个整体原子执行​​。

​可能的重排序问题​

线程A

线程B

执行 (1)dataSource赋值)

​线程切换​​ → 线程B 运行

if (dataSource != null)返回未初始化的 dataSource

dataSource.getConnection()​失败!​

继续执行 (2)(3)(4)

即使 dataSource是 volatile,​​线程B 仍然可能拿到一个未完成初始化的对象​​。

解决方法:先进行初始化再赋值

synchronized (DBUtil.class) {
    if (dataSource == null) {
        MysqlDataSource temp = new MysqlDataSource();  // 临时变量
        temp.setUrl(...);
        temp.setUser(...);
        temp.setPassword(...);
        dataSource = temp;  // 最后一步:赋值给 volatile 变量
    }
}

或者使用静态内部类,如下所示6

4. 静态内部类(Initialization-on-demand Holder)

public class Singleton {
    private Singleton() {}
    
    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}

​特点​​:

  • 延迟初始化

  • 线程安全

  • 无需同步

  • 实现简洁

5. 枚举实现(Enum Singleton)

public enum Singleton {
    INSTANCE;
    
    public void doSomething() {
        // 业务方法
    }
}

​特点​​:

  • 线程安全

  • 防止反射攻击

  • 防止序列化破坏单例

  • 简洁高效

最佳实践建议

  1. 如果不需要延迟加载,推荐使用​​饿汉式​

  2. 如果需要延迟加载,推荐使用​​静态内部类​​方式

  3. 如果需要防止反射和序列化破坏单例,推荐使用​​枚举​​方式

  4. 在Java 5及以上版本,可以使用​​双重检查锁​​方式

单例模式适用于需要全局唯一实例的场景,如配置管理、线程池、数据库连接池等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值