【设计模式】手写一个线程安全的单例模式

文章目录

  • 前言
  • 懒汉式,线程不安全
  • 饿汉式
  • 双重校验锁
  • 使用volatile关键字
  • 静态内部类登记
  • 枚举
  • 使用ThreadLocal实现单例模式(线程安全)
  • 使用CAS锁实现

前言

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

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

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

懒汉式,线程不安全

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

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)