package single;public class Monitor { //开始没有班长,用静态属性表示 private static Monitor monitor=null; //构造方法私有化,防止出现多个班长对象 private Monitor(){}; public static Monitor getmonitor(){ if(monitor==null){ monitor=new Monitor(); } return monitor; }}
-
首先班里没有班长,用静态属性表示.
-
将班长类中的班长属性私有化,防止在其他地方实例化,出现多个班长对象.
-
定义一个获取班长的方法,没有就new一个,有的话就返回已有对象.
以上代码看起来逻辑正确,但是学习过多线程就能够知道,该类存在线程安全问题.
现在有线程A,B,全部在monitor=null的情况下进入方法体中,这样就会有两个班长对象产生,显然是不对的,那么我们让代码更新一下
package single;public class Monitor { //开始没有班长,用静态属性表示 private static Monitor monitor=null; //构造方法私有化,防止出现多个班长对象 private Monitor(){}; public static synchronized Monitor getmonitor(){ if(monitor==null){ monitor=new Monitor(); } return monitor; }}
这样线程安全解决了,但是当多线程全部执行至getmonitor方法时,明显会有多余的等待时间,这样代码执行效率会变低,那么我们再把代码结构优化一下
public class Monitor { // 开始没有班长,用静态属性表示 private static Monitor monitor = null; // 构造方法私有化,防止出现多个班长对象 private Monitor() { }; public static Monitor getmonitor() { if (monitor == null) { //如果没有班长让所有线程进来竞争 synchronized (Monitor.class) { if (monitor == null) //当产生了班长后后面的线程将不会执行循环体 { monitor = new Monitor(); } } } return monitor; }}
这样就明确很多了,这就是所谓的懒汉模式.懒汉模式就是当我们需要的时候,我们就去new对象,但是查询相关资料,我们发现还存在一些问题.
在 monitor = new Monitor();,在这个操作中,JVM主要干了三件事:
1、在堆空间里分配一部分空间;
2、执行 Monitor 的构造方法进行初始化;
3、把 monitor 对象指向在堆空间里分配好的空间。
把第3步执行完,这个 monitor 对象就已经不为空了。
但是,当我们编译的时候,编译器在生成汇编代码的时候会对流程顺序进行优化。优化的结果不是我们可以控制的,有可能是按照1、2、3的顺序执行,也有可能按照1、3、2的顺序执行。
如果是按照1、3、2的顺序执行,恰巧在执行到3的时候(还没执行2),突然跑来了一个线程,进来 getMonitor ()方法之后判断 monitor 不为空就返回了 monitor 实例。此时 monitor 实例虽不为空,但它还没执行构造方法进行初始化(即没有执行2),所以该线程如果对那些需要初始化的参数进行操作那就悲剧了。但是加了 volatile 关键字的话,就不会出现这个问题。这是由 volatitle 本身的特性决定的。
即我们的最终代码为
public class Monitor { // 开始没有班长,用静态属性表示 private static Monitor monitor = null; // 构造方法私有化,防止出现多个班长对象 private Monitor() { }; public static volatitle Monitor getmonitor() { if (monitor == null) { //如果没有班长让所有线程进来竞争 synchronized (Monitor.class) { if (monitor == null) //当产生了班长后后面的线程将不会执行循环体 { monitor = new Monitor(); } } } return monitor; }}