第一种写法:饿汉式
//饿汉式
public class SingleObject1 {
private static final SingleObject1 instance = new SingleObject1();
private SingleObject1(){
}
public static SingleObject1 getInstance(){
return instance;
}
}
第二种写法:懒汉式
1、迭代一:
//懒汉式: 在多线程环境下不安全,会产生多个实例!
public class SingleObject2 {
private static SingleObject2 instance;
private SingleObject2(){
System.out.println("SingleObject2()...");
}
public static SingleObject2 getInstance(){
if(instance == null){
//t1,t2两个线程同时异步来到这里!
instance = new SingleObject2();
}
return instance;
}
public static void main(String[] args) {
//0~4, 循环5次
IntStream.range(0,5).forEach(i ->{
new Thread(() -> {
System.out.println(SingleObject2.getInstance());;
}).start();
});
}
}
- 控制台
SingleObject2()...
SingleObject2()...
com.wzj.线程.单例模式.SingleObject2@7ea6e10f
SingleObject2()...
SingleObject2()...
SingleObject2()...
com.wzj.线程.单例模式.SingleObject2@7fdaab5b
com.wzj.线程.单例模式.SingleObject2@3bbb38b7
com.wzj.线程.单例模式.SingleObject2@1dc727cc
com.wzj.线程.单例模式.SingleObject2@7e611cce
结果: 一看就产生了多个实例,因为构造方法中的输出语句执行了多次,而且对象的地址值也都不一样
分析:是什么原因导致的呢?因为多线程是异步执行的,在某个时间点多个线程可能会同时执行到代码中的①处,然后继续往下执行,可能就会导致该类产生多个实例。既然分析出了原因是因为某个时间点同时进来的多个线程导致,那么我就加锁让他们一个一个同步进来。
2、迭代二:加锁
将getInstance方法改造如下
public static SingleObject2 getInstance(){
synchronized (SingleObject2.class){//类锁
if(instance == null){
instance = new SingleObject2();
}
}
return instance;
}
- 控制台
SingleObject2()...
com.wzj.线程.单例模式.SingleObject2@7ea6e10f
com.wzj.线程.单例模式.SingleObject2@7ea6e10f
com.wzj.线程.单例模式.SingleObject2@7ea6e10f
com.wzj.线程.单例模式.SingleObject2@7ea6e10f
com.wzj.线程.单例模式.SingleObject2@7ea6e10f
加完锁之后,确实变成单例了。但是你有没有发现每次调用该方法执行里面的代码的时候都加锁,这会使得程序的执行效率降低。
3、迭代三:使用双重检查机制
将getInstance方法改造如下
public static SingleObject2 getInstance(){
if(instance == null){ //③处
//①
synchronized (SingleObject2.class){
if(instance == null){//④
instance = new SingleObject2();
}
}
//②
}
return instance;
}
分析:假设两个线程t1,t2执行到了①处,假设t1线程先抢到cpu的执行权,然后获取了锁,这时t2是进不来的,等t1执行到了②处,假设t2线程抢到了cpu的执行权,这时它进去执行if判断结果是不满足条件,因为t1线程已经创建了该类的对象,然后t2也执行完了,这时其他线程进来直接执行③处,由于不为空所以直接返回实例了,要是不加③处的代码,就每个线程进来都得抢锁,然后在④处判断,因为耗时而且没必要,所以在③处加该判断是很有必要的!
4、迭代四:volatile
你天真的以为用了双重检查机制就完事了,小伙子还是太年轻。
双重检查机制可能会出现空指针的问题。这是由于编译器为了优化可能会帮你做的指令重排序!
- 将成员变量instance,使用
volatile
关键字修饰!,它可以禁止重排序!
private static volatile SingleObject2 instance;
5、说清楚什么情况下双重检查机制会导致空指针的问题!
- 题外话
我为什么会问这个呢?因为不是我问的,哈哈哈,因为今天我在写单例的时候,由于是多线程的访问,我就将它写成了这种双重检查+volatile的形式,然后提交后,不料就被function leader给看到了,就连环炮来问我,问我这样写的好处,我就一一跟它说了,他笑着说没见过加类锁这种形式,说我写的不规范,我:额,哪里不规范了,还说应该拿instance作为锁,我说第一次进来instance不是空的吗,怎么可以作为锁呢?这不是导致空指针吗,他沉默了,然后他要问为什么要加两次判断,这不是多余吗,我还是跟他解释,他又沉默了。然后他又问为什么要加volatile,我刚开始也不是特别清楚,我就和他说只知道是编译器为了优化帮我们做了重排序,可能会导致空指针。所以我加上volatile关键字禁止重排序,这样就可以避免这种情况的发生,然后他说这怎么会空指针啊,就算重排序也没问题啊,他理解的重排序是编写代码的时候互换位置,然后又说不要在网上看了一下就直接用,突显你会新技术啊,这可是要投产的,所以你看你加这个volatile根本什么用都没有,写的多余,耗费性能,我:额,耗费个屁的性能。要说性能这项目中好多代码得重构。当然这是我心里想的,我表明就笑笑不说话,你是大哥,你爱咋样咋样!。
- 所以下面我就要分析TMD不加
volatile
可能会发生空指针!
public class SingleObject2 {
private static SingleObject2 instance;
private String name;
public String getName(){
return this.name;
}
private SingleObject2(){
System.out.println("SingleObject2()...");
this.name = "wzj";
}
public static SingleObject2 getInstance(){
if(instance == null){
synchronized (SingleObject2.class){
if(instance == null){
instance = new SingleObject2(); //①
}
}
}
return instance;
}
public static void main(String[] args) {
//0~4, 循环5次
IntStream.range(0,5).forEach(i ->{
new Thread(() -> {
System.out.println(SingleObject2.getInstance().getName());;
}).start();
});
}
}
在①处其实 new SingleObject2(),其实不是一个原子性的操作,它可以分为三步
1、分配内存空间
2、初始化对象
3、将对象指向刚分配的内存空间
但是有些编译器为了性能的原因,可能会将第二步和第三步进行重排序,顺序就成了:
1、分配内存空间
2、将对象指向刚分配的内存空间
3、初始化对象
- 于是可能就发生如下表格情况,但我运行了好多次,始终模拟不到不加
volatile
会发生空指针的现象,这也是跟线程具有随机的特性有关。
- 这里加
volatile
是为了禁止指令重排序,保证有序性!
第三种写法:使用静态内部类(推荐)
//使用静态内部类的方式
public class SingleObject3 {
private SingleObject3(){}
private static class InstanceHolder{
private final static SingleObject3 instance = new SingleObject3();
}
public static SingleObject3 getInstance(){
return InstanceHolder.instance;
}
}
第四种写法:使用枚举(推荐)
//使用枚举来实现单例,很好,但是个人感觉不太优雅,可能是我太菜了!
public class SingleObject4 {
private SingleObject4(){}
private enum Singleton{
INSTANCE;
private final SingleObject4 instance;
Singleton(){
instance = new SingleObject4();
}
public SingleObject4 getInstance(){
return instance;
}
}
public static SingleObject4 getInstance(){
return Singleton.INSTANCE.getInstance();
}
public static void main(String[] args) {
IntStream.rangeClosed(0,100)
.forEach(i -> new Thread(String.valueOf(i)){
@Override
public void run() {
System.out.println(SingleObject4.getInstance());
}
}.start());
}
}
最后:来自虽然帅,但是菜的cxy