Java中创建单例模式的五种方法及线程安全

本文深入探讨了单例模式在线程安全环境下的实现方法,对比分析了五种不同的实现方案,并指出其中的优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

        单例模式在日常工程项目中应用十分广泛,但是如果没有考虑线程安全的问题,可能会在多线程环境下产生错误的结果,生成多个实例。

公共类

        以下两个类,是本次实验中用到的两个公共类,SingletonFactory用于创建各种单例对象,SingletonTest用于测试各种单例模式的线程安全性。

public interface SingletonFactory {
    public static SingletonImit getSingletonImit() {
        return SingletonImit.getInstance();
    }

    public static SingletonDCL getSingletonDCL() {
        return SingletonDCL.getInstance();
    }

    public static SingletonStaticBlock getSingletonStaticBlock() {
        return SingletonStaticBlock.getInstance();
    }

    public static SingletonInnerStaticClass getSingletonInnerStaticClass() {
        return SingletonInnerStaticClass.getInstance();
    }

    public static SingletonInnerEnum getSingletonInnerEnum() {
        return SingletonInnerEnum.getInstance();
    }

    public static SingletonWrong getSingletonWrong() {
        return SingletonWrong.getInstance();
    }
}
public class SingletonTest {
    public static void main(String[] args) {
        Thread[] threads = new Thread[5];
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("线程:" + Thread.currentThread().getName() + "创建了单例对象, 其hashcode值为:" + SingletonFactory.getSingletonWrong().hashCode());
            }
        };

        for (int i = 0; i < 5; i++) {
            threads[i] = new Thread(runnable);
            threads[i].setName(i + 1 + "");
            threads[i].start();
        }

    }
}

一个错误的示范

        以下将要展示的是一种错误的单例模式。

public class SingletonWrong {

    private volatile static SingletonWrong instance;

    private SingletonWrong() {

    }

    public static SingletonWrong getInstance() {
        try {
            if (instance == null) {
                Thread.sleep(3000);
                instance = new SingletonWrong();

            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return instance;
    }

}

        该程序在测试环境中运行结果如下:

这里写图片描述

        从hashcode我们可以看出,多线程使用单例模式时,此种方法并没有达到单例的效果,而是创建了多个实例。

五种线程安全的单例模式实现方式

        1. 使用“立即加载”模式:

public class SingletonImit {
    private static SingletonImit instance = new SingletonImit();

    private SingletonImit() {
    }

    public static SingletonImit getInstance() {
        return instance;
    }

}

        该实现在测试环境中运行结果如下:

这里写图片描述

        从结果上我们可以看到,多线程下达到了获得单例的目的。不过此种方式的缺陷在于:在类加载过程中就已经将具体的对象创建完毕,如果对象很大,会提前一直占用着内存。

        2. 使用“延时加载”实现线程安全的单例模式:

public class SingletonDCL {
    private volatile static SingletonDCL instance;

    private SingletonDCL() {

    }

    public static SingletonDCL getInstance() {
        try {
            if (instance == null) {
                Thread.sleep(3000);
                synchronized (SingletonDCL.class) {
                    if (instance == null)
                        instance = new SingletonDCL();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return instance;
    }
}

        在测试环境中运行结果如下:
这里写图片描述

        此种方式实际上是对上面展示的“错误示范”的一种改进,可以看到,新的实现方式达到了单例的目的。

        3. 利用静态内部类实现线程安全的单例模式:

public class SingletonInnerStaticClass implements Serializable {
    private static final long serialVersionUID = 888L;

    private SingletonInnerStaticClass() {
    }

    private static class SingleHandler {
        private static SingletonInnerStaticClass instatnce = new SingletonInnerStaticClass();

    }

    public static SingletonInnerStaticClass getInstance() {
        return SingleHandler.instatnce;
    }

    protected Object readResolve() throws ObjectStreamException {
        return SingleHandler.instatnce;
    }
}

        该实现方式在测试环境中运行结果如下:

这里写图片描述

        此种方式需要注意的是在单例对象需要序列化时,需要重写protect Object readResolve()方法。

        4. 利用“静态代码块”实现单例模式:

public class SingletonStaticBlock {
    private static SingletonStaticBlock instance;

    static {
        instance = new SingletonStaticBlock();
    }

    private SingletonStaticBlock() {
    }

    public static SingletonStaticBlock getInstance() {
        return instance;
    }
}

         此实现方式在测试环境中运行结果如下:

这里写图片描述

        此种实现方法与“立即加载”的原理基本相同,都是利用类加载时的初始化操作实现的。

        5. 利用“内部枚举类型”实现线程安全的单例模式:

public class SingletonInnerEnum {
    private SingletonInnerEnum() {
    }

    public enum Instance {
        SingleinnerEnum;
        private SingletonInnerEnum instance;

        private Instance() {
            try {
                Thread.sleep(3000);
                instance = new SingletonInnerEnum();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        public SingletonInnerEnum getInstance() {
            return instance;
        }
    }

    public static SingletonInnerEnum getInstance() {
        return Instance.SingleinnerEnum.getInstance();
    }
}

        此方法在测试环境中结果如下:

这里写图片描述

        可以从结果看到,此种方式确实能够在多线程环境下实现单例的目的。

结语

        上面虽然介绍了五种实现单例模式的方法,但只是具体的实现方式不同,实质上只有“立即加载”与“延迟加载”两种。特别需要注意的是使用“延迟加载”时,需要两重检测,否则即使存在同步块,也无法保证线程安全。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值