优雅的手写一个线程安全的单例模式

单例模式是我们非常常用的设计模式之一。百度百科给出的定义:

单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)

一般,我们为了实现单例模式,通常把构造器私有化,然后通过静态方法和静态变量来获取一个对象。

懒汉式,线程不安全

这种方式是最基础的实现方式,最大的问题是不支持多线程,在多线程模式下会产生线程安全问题。

public class Singleton {

    private static Singleton singleton;

    private Singleton() {}

    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

所以我们给getInstance()加synchronized锁,保证它在多线程的情况下使用。但会影响效率。

public class Singleton {

    private Singleton singleton;
    private Singleton(){};
    public synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }

}

饿汉式

比较常用的一种方法,使用classLoder机制避免了多线程同步问题。但是会产生垃圾对象,浪费内存。

public class Singleton {

    private static Singleton instance = new Singleton();
    private Singleton(){}
    public static Singleton getInstance() {
        return instance;
    }

}

双重校验锁

采用懒加载模式,同样也是线程安全的。采用双锁机制,在多线程模式下也能保持高性能

/**
 * 懒汉式单例模式(线程不安全)
 * */
public class Singleton {

    private static Singleton singleton;

    private Singleton() {}

    public static Singleton getInstance() {
        if (singleton ==  null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

使用volatile关键字

上面双重校验锁可能会出现如下线程问题:
在 Singleton 构造函数体执行之前,变量 instance 可能成为非 null 的,即赋值语句在对象实例化之前调用,此时别的线程得到的是一个还会初始化的对象, 也就是 一个线程走到3正在初始化时被挂起,另一个线程走到0,发现instance不为null,就直接返回一个没有初始化完全的对象!
所以为了避免这种情况发生,我们给instance加上volatile关键字,利用它禁止指令重排序的功能,给写操作添加一个内存屏障。即在完全的初始化完一个对象前,不会调用读操作。

public class Singleton {

    private volatile static Singleton singleton;

    private Singleton() {}

    public static Singleton getInstance() {
        if (singleton ==  null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

静态内部类登记

和饿汉式同样,使用ClassLoader机制来保证了初始化instance只有一个线程。而和饿汉式不一样的地方在于,使用了Lazy初始化,只有在主动调用getInstance方法时,才会实例化对象。

/**
 * 懒汉式单例模式(线程不安全)
 * */
public class Singleton {

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    private Singleton() {}

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }

}

枚举

采用单例模式的最佳方法,简介、自动支持序列化机制,而且避免了线程同步问题。不过目前很少被使用。

public enum  Singleton {
    INSTANCE;

    public void whateverMethod() {}

}

使用ThreadLocal实现单例模式(线程安全)

ThreadLocal为每个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。

/**
 * 使用ThreadLocal实现单例模式(线程安全)
 * */
public class Singleton {

    private static final ThreadLocal<Singleton> tlSingleton = new ThreadLocal<Singleton>() {
        @Override
        protected Singleton initialValue() {
            return new Singleton();
        }
    };

    private Singleton(){}

    public static Singleton getInstance() {
        return tlSingleton.get();
    }

}

使用CAS锁实现

import java.util.concurrent.atomic.AtomicReference;

/**
 * CAS
 * */
public class Singleton {

    private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<>();

    private Singleton(){}

    public static Singleton getInstance() {
        for (;;) {
            Singleton current = INSTANCE.get();
            if (current != null) {
                return current;
            }
            current = new Singleton();
            if (INSTANCE.compareAndSet(null , current)) {
                return current;
            }
        }
    }
}
手写一个线程安全单例模式,可以使用懒汉式和双重检查锁定的方式来实现。懒汉式是指在需要使用单例对象时才去创建它,双重检查锁定则是为了保证在多线程环境下只有一个线程能够创建该对象。 以下是一个示例代码: ```java public class Singleton { private static volatile Singleton instance; private Singleton() { // 私有构造方法 } public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } } ``` 在这个示例中,我们使用了volatile关键字来确保instance变量在多线程环境下的可见性。在getInstance()方法中,首先检查instance是否为null,如果为null,则进入同步块。在同步块内部再次检查instance是否为null,这是为了防止多个线程同时通过了第一个检查,进入同步块后再创建多个实例的情况。如果instance仍然为null,才会创建一个新的Singleton实例并赋值给instance变量。 需要注意的是,双重检查锁定方式只在Java 5及以上版本才能保证正确性,因为在Java 5之前的版本中,volatile关键字的语义不够强大,可能会导致创建出多个实例。而在Java 5及以上版本中,通过volatile关键字能够确保instance变量的可见性,并且禁止指令重排序,从而保证线程安全。 以上是一种手写线程安全单例模式的实现方式,通过使用双重检查锁定和volatile关键字,能够在多线程环境下保证只有一个实例被创建。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Java--设计模式单例模式+保证线程安全的5种写法(懒、汉、内、双、枚)](https://blog.csdn.net/JustinQin/article/details/120668592)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值