话不多说,首先上代码!!!
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 singletion;
}
}
对于上面的代码,主要的疑问点可能集中于下面的几个,我将一一说明。
(1)为什么需要两次if判断?各自起的作用是什么?
(2)为什么需要volatile修饰变量?
(3)synchronized关键字的使用方式?
1)两次if判断与校验
a.第一次if(singleton ==null)所起的作用主要是作为提高代码的执行效率,因为是单例模式,所以如果已经创建过对象了,所以在进入实例化方法getInstance()时就不需要进入到同步代码块中去了。
b.第二次if(singleton==null)所起的作用主要是为了避免在同步代码块内创建重复的多个对象。用人话说是啥意思呢?我说个例子,你就明白了。如果这个时候有两个线程T1与T2,两个线程几乎同时进入到getInstance()方法中,但是T1稍微快一丢丢,singleton还未被实例化还是为null,这个时候T1线程首先进入到了同步代码块中,但是还未实例化对象,此时singleton还是为空,这时,T2线程通过第一层判断,因为对象还未被实例化,所以T2等待T1处理(等待在同步代码块外),接着T1通过第二层if判断,完成实例化创建好对象,然后完成任务,这个时候资源回到T2线程,T2线程开始进入到同步代码块,如果没有第二个if判断,那么T2线程将再创建一个自己的实例对象,但是现实情况是T1已经在之前创建了实例对象,所以如果不加第二层判断,会导致多个线程产生多个实例化对象。
2)Volatile修饰
对于为社么需要给变量添加这个关键字,为什么是必不可少的呢?
volatile在这里主要的作用为两个方面。
a.防止JVM指令重排的发生;
分析:
对于singleton = new Singleton();这行代码可以分为三个步骤:
1.给singleton分配内存空间;
2.初始化singleton;
3.将singlton指向分配的内存空间;
但是在实际的运行中,由于JVM的指令重排,可能会导致实际的运行顺序由上面的123变为132。
指令重排在单线程下不会出现问题,但是在多线程下会导致一个线程获得一个未初始化的实例。例如:线程T1执行了1和3,此时T2调用 getInstance() 后发现 singleton 不为空,因此返回 singleton, 但是此时的 singleton 还没有被初始化。
使用 volatile 会禁止JVM指令重排,从而保证在多线程下也能正常执行。
b.保证变量再多线程运行时的可见性。
指令重排在单线程下不会出现问题,但是在多线程下会导致一个线程获得一个未初始化的实例。例如:线程T1执行了1和3,此时T2调用 getInstance() 后发现 singleton 不为空,因此返回 singleton, 但是此时的 singleton 还没有被初始化。
使用 volatile 会禁止JVM指令重排,从而保证在多线程下也能正常执行。
3)synchronized 关键字的使用方法
1.修饰实例方法,相当于给当前对象实例加锁,进入同步代码块之前要获得当前对象实例的锁。
2.修饰静态方法,相当于给当前类对象加锁,进入同步代码块之前需要获取当前类对象的锁。
3.修饰同步代码块,指定对象,给指定对象加锁。在进入同步代码块之前要先获取给定对象的锁。
和 synchronized 方
法一样, synchronized(this) 代码块也是锁定当前对象的。 synchronized 关键字加到 static 静态方法和
synchronized(class) 代码块上都是是给 Class 类上锁。这里再提一下: synchronized 关键字加到非 static 静态方法上是给对象实例上锁。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)