侧边栏壁纸
博主头像
LYMTICS

海纳百川,有容乃大

  • 累计撰写 45 篇文章
  • 累计创建 37 个标签
  • 累计收到 19 条评论

目 录CONTENT

文章目录

JUC单例模式练习

LYMTICS
2022-02-28 / 0 评论 / 2 点赞 / 85 阅读 / 3,405 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-02-28,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

JUC单例模式练习

版本一:线程不安全

先实现一个线程不安全的单例模式

package singleton;

public class SingleThreadedSingleton {
	// 保存唯一的实例
	private static SingleThreadedSingleton instance = null;
	
	// 省略实例变量声明
	
	// 无法在外部构造
	private SingleThreadedSingleton() {}
	
	public static SingleThreadedSingleton getInstance() {
		// check-then-act
		if (null == instance) {
			System.out.println("生成了一次实例");
			instance = new SingleThreadedSingleton();
		}
		return instance;
	}
	
	public void someService() {
		System.out.println("Do Some Service");
	}
}

编写一个测试类:

package singleton;

public class SingletonTest {
	public static void main(String[] args) {
		for (int i = 0; i < 5; i++) {
			new Thread(new Simulate()).start();
		}
	}
	
	public static class Simulate implements Runnable{
		@Override
		public void run() {
			System.out.println(SingleThreadedSingleton.getInstance());
		}
	}
}

输出:

生成了一次实例
生成了一次实例
singleton.SingleThreadedSingleton@7438001b
生成了一次实例
singleton.SingleThreadedSingleton@327ff0ac
生成了一次实例
singleton.SingleThreadedSingleton@3b979fa1
生成了一次实例
singleton.SingleThreadedSingleton@609221bf
singleton.SingleThreadedSingleton@68f93713

版本二:使用内部锁

只需把上面 check-then-act 的部分添加一个内部锁就可以了:

synchronized (SingleThreadedSingleton.class) {
    if (null == instance) {
        System.out.println("生成了一次实例");
        instance = new SingleThreadedSingleton();
    }
}

输出:

生成了一次实例
singleton.SingleThreadedSingleton@3b979fa1
singleton.SingleThreadedSingleton@3b979fa1
singleton.SingleThreadedSingleton@3b979fa1
singleton.SingleThreadedSingleton@3b979fa1
singleton.SingleThreadedSingleton@3b979fa1

可以看到确实只生成了一个实例

版本三:双重检查锁定

DCL(Double-checked Locking)

主要的问题是,上述情景中,实例其实就生成一次,但是每次获取实例都要检查锁、获取锁,而其实实例生成后就不用再担心线程安全的问题了,所以这会造成不必要的性能损耗。

可以把关键代码修改如下:(下面类名修改了,但是没有影响)

if (null == instance) {
    synchronized (IncorrectDCLSingleton.class) {
        if (null == instance) {
            System.out.println("生成了一次实例");
            instance = new IncorrectDCLSingleton();
        }
    }
}
return instance;

这样似乎可以奏效。

但是,根据《Java多线程编程指南》的描述,这样仍然是不安全的!

原因是:

instance = new IncorrectDCLSingleton();

这条指令可以分解为以下伪代码所示的几个独立子操作:

objRef = allocate(IncorrectDCLSingleton);	//1
invokeConstructor(objRef);		//2
instance = objRef		//3

根据重排序规则,临界区内的操作可以在临界区内被重排序,所以假如上述代码被重排序为:1->3->2 的话,此时 objRef 还没有初始化,然后发生了线程切换,另一个线程就会通过 if 判断而返回一个还没有实例化的实例。

解决这个问题得保障有序性,既可以给 instance 实例添加一个 volatile 关键词。

版本四:静态内部类

这个方法可以实现延迟加载的效果,并且比较简单。

利用了类的静态变量只会被创建一次。(思考,为什么?)

package singleton;

public class StaticHolderSingleton {
	// 省略实例变量声明
	
	// 无法在外部构造
	private StaticHolderSingleton() {
		System.out.println("StaticHolderSingleton Inited.");
	}
	
	private static class InstanceHolder {
		final static StaticHolderSingleton INSTANCE = new StaticHolderSingleton(); 
	}
	
	public static StaticHolderSingleton getInstance() {
		System.out.println("getInstance invoked.");
		return InstanceHolder.INSTANCE;
	}
	
	public void someService() {
		System.out.println("Do Some Service.");
	}
}

输出:

getInstance invoked.
getInstance invoked.
getInstance invoked.
getInstance invoked.
getInstance invoked.
StaticHolderSingleton Inited.
singleton.StaticHolderSingleton@3fa80c0b
singleton.StaticHolderSingleton@3fa80c0b
singleton.StaticHolderSingleton@3fa80c0b
singleton.StaticHolderSingleton@3fa80c0b
singleton.StaticHolderSingleton@3fa80c0b

版本五:枚举类

这个要建一个枚举类:

package singleton;

public class SingletonTest {
	public static void main(String[] args) {
		for (int i = 0; i < 5; i++) {
			new Thread(new Simulate()).start();
		}
	}
	
	public static class Simulate implements Runnable {
		@Override
		public void run() {
			Singleton.INSTANCE.someService();
		}
	}
	public static enum Singleton {
		INSTANCE;
		Singleton() {
			System.out.println("Singleton inited.");
		}
		
		public void someService() {
			System.out.println("Do some Service");
		}
	}
}

总结

本文是学习《Java多线程编程实战指南》的练习。

其实上面的几种方式,还有另外的名字,可以参考这篇文章:单例模式 | 菜鸟教程 (runoob.com)

2

评论区