赞
踩
目录
一、懒汉式单例(未经增强、存在线程安全问题,多个线程同时访问可能导致多个实例的创建)
单例模式是一种常见的设计模式,用于确保某个类只有一个实例,并提供全局访问点以供其他对象使用。在Java中,单例模式的实现方式主要有懒汉式单例、饿汉式单例和登记式单例。
懒汉式单例是指在首次使用时才创建实例。其特点是延迟加载,即在需要的时候才创建对象。懒汉式单例的写法通常采用双重检查锁定来保证线程安全。
饿汉式单例是指在类加载时就创建实例。其特点是立即加载,即在类加载时就生成对象。饿汉式单例的写法简单直接,但无法实现延迟加载。
登记式单例是指将所有实例都存放在一个注册表中,通过标识符来获取对应的实例。登记式单例的写法相对复杂,但可以灵活地管理多个实例。
单例模式具有以下特点:
在计算机系统中,常有一些资源需要全局共享访问,例如线程池、缓存、日志对象、对话框、打印机等。这些应用通常使用单例模式来管理资源,确保只有一个实例存在,并且提供全局访问点,避免不一致状态和多重资源管理问题的发生。
- //懒汉式单例类.在第一次调用的时候实例化自己
- public class Singleton {
- private Singleton() {}
- private static Singleton single=null;
- //静态工厂方法
- public static Singleton getInstance() {
- if (single == null) {
- single = new Singleton();
- }
- return single;
- }
- }
该代码中的Singleton
类有一个私有的构造方法,用于限制外部通过new
关键字实例化对象。它还有一个私有的静态变量single
,用于保存单例对象。静态工厂方法getInstance()
实现了延迟加载,在第一次调用时才会进行实例化操作。
在getInstance()
方法中,首先判断single
是否为null,如果为null则创建一个新的Singleton
对象,并将其赋值给single
变量。然后返回single
变量,保证每次调用getInstance()
方法时都能获得同一个单例对象。
需要注意的是,该实现方式在多线程环境下可能存在线程安全问题。当多个线程同时调用getInstance()
方法并且single
为null时,可能会导致创建多个实例。为了解决这个问题,可以在getInstance()
方法中添加双重检查锁定机制或者使用静态内部类的方式来实现懒汉式单例,保证线程安全。
- public static synchronized Singleton getInstance() {
- if (single == null) {
- single = new Singleton();
- }
- return single;
- }
通过将getInstance()
方法声明为synchronized
,可以保证在同一时刻只有一个线程能够进入该方法。当多个线程同时调用getInstance()
时,其他线程将被阻塞,直到获取到锁才能继续执行。
这样做确实解决了线程安全问题,保证了在多线程环境下只创建一个实例。但是需要注意的是,使用synchronized
关键字会带来一定的性能损耗,因为每次调用getInstance()
方法都会进行同步操作。
- public static Singleton getInstance() {
- if (singleton == null) {
- synchronized (Singleton.class) {
- if (singleton == null) {
- singleton = new Singleton();
- }
- }
- }
- return singleton;
- }
在方法内部,首先判断singleton对象是否为null。如果是null,表示还没有创建过Singleton对象。
接着,进入 synchronized (Singleton.class) 代码块,该代码块使用Singleton类作为锁。这是为了保证多线程环境下只有一个线程可以进入创建实例的逻辑。
在 synchronized 代码块内部,再次判断singleton是否为null。这个双重检查的目的是为了避免多个线程都通过第一个判断,进入synchronized代码块后,如果直接创建了实例,其他线程也能够得到非空的singleton,从而导致创建多个实例。
如果第二次判断singleton为null,表示没有其他线程创建过实例,这时候我们创建一个新的Singleton对象并赋值给singleton变量。
最后,返回singleton对象。
通过双重检查加锁机制在多线程环境下保证了只有一个实例被创建,并且延迟了实例的创建时间,只有在需要时才会创建对象。这样可以提高程序的性能和资源利用率。
为什么不这样写呢,非要双重检查锁定吗?这样也能实现线程安全
- public static Singleton getInstance() {
- synchronized (Singleton.class) {
- if (singleton == null) {
- singleton = new Singleton();
- }
- }
- return singleton;
- }
只有在singleton对象为null的情况下才会进入同步块,这是为了确保只有一个线程能够创建实例。但是,如果singleton对象已经被创建,每次调用getInstance()方法时都会进行同步操作,这会导致性能下降。
- public class Singleton {
- private static class LazyHolder {
- private static final Singleton INSTANCE = new Singleton();
- }
- private Singleton (){}
- public static final Singleton getInstance() {
- return LazyHolder.INSTANCE;
- }
- }
这种静态内部类实现单例模式的方式是线程安全的并且同时保证了性能,主要有以下几个原因:
类加载机制:静态内部类在被调用前不会进行加载,也就是说,只有在getInstance()方法被调用时才会加载LazyHolder类。在类加载的过程中,JVM会保证线程安全,确保只有一个线程能够完成类的加载并初始化LazyHolder.INSTANCE对象。
final关键字:在静态内部类LazyHolder中,我们使用了final关键字修饰INSTANCE对象。final关键字保证了该对象的引用不可变,即一旦LazyHolder.INSTANCE被赋值,就无法再被修改。这样可以防止其他线程对INSTANCE对象进行修改或重新赋值。
唯一的实例:由于LazyHolder.INSTANCE是静态的且final的,它只会被初始化一次。在整个程序执行过程中,无论有多少线程调用getInstance()方法,只会返回同一个INSTANCE对象。
- //饿汉式单例类.在类初始化时,已经自行实例化
- public class Singleton1 {
- private Singleton1() {}
- private static final Singleton1 single = new Singleton1();
- //静态工厂方法
- public static Singleton1 getInstance() {
- return single;
- }
- }
在类初始化时,静态变量 single
会被立即实例化并初始化为一个 Singleton1
对象。因此,这个实例将在整个程序生命周期中保持不变。
由于是在类加载的过程中实例化对象,所以可以保证线程安全性。在多线程环境下,无论是哪个线程首次调用 getInstance()
方法,都会获得同一个 single
实例,因为该实例已经在类加载时被创建。
需要注意的是,由于这是饿汉式的实现方式,即使在实际使用中不需要获取单例实例,也会进行实例化,可能会浪费一些资源。另外,这种实现方式不支持延迟加载,即在实际需要使用单例实例之前就已经创建了对象。
饿汉式和懒汉式是两种常见的单例设计模式实现方式。区别主要在于初始化时机、线程安全性和性能方面。
针对懒汉式的非线程安全问题,可以采取以下几种方式来实现线程安全:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。