说说设计模式 -- 单例模式
一)单例模式的目的
单例模式的目的是仅创建一个类的实例。
二)思考如何构建单例
在这里我们将用于构建该单例的类命名为MyProject,因为创建该实例的行为只能发生在MyProject的内部。而即便MyProject是包级私有(缺省修饰符:default)的,同一包下其它类仍可以访问到MyProject,因此为了避免同包其它类对MyProject构造器的访问,需要将构造器的访问权限定义为private。但这里会导致一个问题,若想使用这个私有的构造器必须先有MyProject实例,但是MyProject实例却必须通过这个构造器产生,因此这里会陷入一个死循环。
不过我们可以通过另外一种方式来实例化MyProject,见如下代码:
1 public class MyProject { 2 3 private static MyProject myProject; 4 5 private MyProject() 6 { 7 8 } 9 10 public static MyProject getInstance() 11 { 12 if(myProject == null) 13 { 14 myProject = new MyProject(); 15 } 16 17 return myProject; 18 } 19 20 }
三)经典单例模式的局限及其优化方案
以上代码是常见的经典单例模式代码,但是在多线程场景下却隐藏着问题,可能会得到两个MyProject实例,这是因为两个线程之间的执行会互相切换(由CPU负责调度),可能线程一执行一半时切换到线程二再去执行,当线程二执行一半时可能又会切回线程一去执行,虽然二者最终都会执行完毕,但对于结果的正确性却不能做保证。
因此在上述getInstance()方法定义时,需要加入synchronized关键字,但是该关键字的加入却会带来性能上的影响,因为只有在第一次创建MyProject时才会真的需要同步,一旦单个实例被创建成功,其它线程对该实例就不会造成什么影响,导致synchronized关键字就没有了存在的意义。那么有什么方法可以解决上述问题呢?
以下是经过修改后的代码:
1 public class MyProject { 2 3 private volatile static MyProject myProject; 4 5 private MyProject() 6 { 7 8 } 9 10 public static MyProject getInstance() 11 { 12 if(myProject == null) 13 { 14 synchronized (MyProject.class) 15 { 16 if(myProject == null) 17 { 18 myProject = new MyProject(); 19 } 20 } 21 } 22 23 return myProject; 24 } 25 26 }
在myProject变量定义时加入了关键字volatile,该关键字的作用是当其它线程访问该变量时,会获取该变量的最新值,即保证所有线程访问到的这个变量值是一致的。在加入了这个关键字后,后续的线程再去调用这个方法时,执行第12行的if判断发现实例已经存在则直接执行23行的return返回实例,这样就不会触发synchronized同步块导致性能影响了。