单例模式的9中实现方式
什么是单例模式
内存中只允许一个对象实例,此时 我们选择使用单例设计模式
单例模式的特点
1、单例模式只能有一个实例。
2、单例类必须创建自己的唯一实例。
3、单例类必须向其他对象提供这一实例。
B站视频 同步
喜欢看视频的朋友,可以点击小程序卡片,查看!
单例模式的实现
1. 懒汉模式(线程不安全)
public class SingletonDemo {
private static SingletonDemo instance;
private SingletonDemo(){
}
public static SingletonDemo getInstance(){
if(instance==null){
instance=new SingletonDemo();
}
return instance;
}
}
如上,通过提供一个静态的对象instance,利用private权限的构造方法和getInstance()方法来给予访问者一个单例。
缺点是,没有考虑到线程安全。
2. 线程安全的懒汉模式(线程安全)
public class SingletonDemo {
private static SingletonDemo instance;
private SingletonDemo(){
}
public static synchronized SingletonDemo getInstance(){
if(instance==null){
instance=new SingletonDemo();
}
return instance;
}
}
3. 饿汉模式(线程安全)
public class SingletonDemo {
private static SingletonDemo instance=new SingletonDemo();
private SingletonDemo(){
}
public static SingletonDemo getInstance(){
return instance;
}
}
直接在运行这个类的时候进行一次loading,之后直接访问。
4. 静态类内部加载(线程安全)
public class SingletonDemo {
private static class SingletonHolder{
private static SingletonDemo instance=new SingletonDemo();
}
private SingletonDemo(){
System.out.println("Singleton has loaded");
}
public static SingletonDemo getInstance(){
return SingletonHolder.instance;
}
}
使用内部类的好处是,静态内部类不会在单例加载时就加载,而是在调用getInstance()方法时才进行加载,达到了类似懒汉模式的效果,而这种方法又是线程安全的。
5. 枚举方法(线程安全)
enum SingletonDemo{
INSTANCE;
public void otherMethods(){
System.out.println("Something");
}
}
Effective Java作者Josh Bloch 提倡的方式,在我看来简直是来自神的写法。解决了以下三个问题:
(1)自由串行化。
(2)保证只有一个实例。
(3)线程安全。
如果我们想调用它的方法时,仅需要以下操作:
public class Hello {
public static void main(String[] args){
SingletonDemo.INSTANCE.otherMethods();
}
}
这种充满美感的代码真的已经终结了其他一切实现方法了。
6. 双重校验锁法(通常线程安全,低概率不安全)
public class SingletonDemo {
private static SingletonDemo instance;
private SingletonDemo(){
System.out.println("Singleton has loaded");
}
public static SingletonDemo getInstance(){
if(instance==null){
synchronized (SingletonDemo.class){
if(instance==null){
instance=new SingletonDemo();
}
}
}
return instance;
}
}
接下来我解释一下在并发时,双重校验锁法会有怎样的情景:
STEP 1. 线程A访问getInstance()方法,因为单例还没有实例化,所以进入了锁定块。
STEP 2. 线程B访问getInstance()方法,因为单例还没有实例化,得以访问接下来代码块,而接下来代码块已经被线程1锁定。
STEP 3. 线程A进入下一判断,因为单例还没有实例化,所以进行单例实例化,成功实例化后退出代码块,解除锁定。
STEP 4. 线程B进入接下来代码块,锁定线程,进入下一判断,因为已经实例化,退出代码块,解除锁定。
STEP 5. 线程A获取到了单例实例并返回,线程B没有获取到单例并返回Null。
理论上双重校验锁法是线程安全的,并且,这种方法实现了lazyloading。
7. 第七种终极版 (volatile)
对于6中Double-Check这种可能出现的问题(当然这种概率已经非常小了,但毕竟还是有的嘛~),解决方案是:只需要给instance的声明加上volatile关键字即可,volatile版本如下:
public class Singleton{
private volatile static Singleton singleton = null;
private Singleton() { }
public static Singleton getInstance() {
if (singleton== null) {
synchronized (Singleton.class) {
if (singleton== null) {
singleton= new Singleton();
}
}
}
return singleton;
}
}
volatile关键字的一个作用是禁止指令重排,把instance声明为volatile之后,对它的写操作就会有一个内存屏障(什么是内存屏障?),这样,在它的赋值完成之前,就不用会调用读操作。
8. 使用ThreadLocal实现单例模式(线程安全)
public class Singleton {
private static final ThreadLocal
tlSingleton =
new ThreadLocal
() {
@
Override
protected Singleton initialValue() {
return
new Singleton();
}
};
/**
* Get the focus finder for this thread.
*/
public static Singleton getInstance() {
return tlSingleton.
get();
}
// enforce thread local access
private Singleton() {}
}
ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。
9. 使用CAS锁实现(线程安全)
/**
* 更加优美的Singleton, 线程安全的
*/
public class Singleton {
/** 利用AtomicReference */
private static final AtomicReference
INSTANCE =
new AtomicReference
();
/**
* 私有化
*/
private Singleton(){
}
/**
* 用CAS确保线程安全
*/
public static final Singleton getInstance(){
for (;;) {
Singleton current = INSTANCE.
get();
if (current !=
null) {
return current;
}
current =
new Singleton();
if (INSTANCE.compareAndSet(
null, current)) {
return current;
}
}
}
public static void main(String[] args) {
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
System.
out.println(singleton1 == singleton2);
}
}
hello,我是御风,很高兴在这里与你相遇。我是一名程序员,也是一名B站UP主。我乐于分享,勤于学习。希望我们共同成长。永远相信梦想的力量
给个[在看],是对御风最大的支持