synchronized在单例模式中的使用
在单例模式中有一种懒汉式的单例,就是类初始化的时候不创建对象。等第一次获取的时候再创建对象。这种单例在单线程下是没有问题的获取的也都是同一个对象。但是如果放入多线程中就会获取多个不同对象问题。
1、首先来看一个懒汉式的单例模式:
1 2 3 4 5 6 7 8 9 10 11 | //懒汉式的单例类 class MyJvm{ private static MyJvm instance = null ; private MyJvm(){} public static MyJvm getInstance(){ if (instance == null ) { instance = new MyJvm(); } return instance; } } |
测试类:
1 2 3 4 5 6 7 | public static void main(String[] args) { MyJvm jvm1 = MyJvm.getInstance(); MyJvm jvm2 = MyJvm.getInstance(); System.out.println(jvm1); System.out.println(jvm2); } |
测试结果:单线程的情况下,懒汉式的结果是没问题的。
com.fz.Thread.syn.MyJvm@18f1d7e
com.fz.Thread.syn.MyJvm@18f1d7e
2、现在加入多线程访问的情况
编写一个多线程的类:
1 2 3 4 5 6 7 | //多线程类 class MyJvmThread extends Thread{ @Override public void run() { System.out.println(Thread.currentThread().getName()+ "-->创建:" +MyJvm.getInstance()); } } |
然后再使用多线程测试:
1 2 3 4 5 6 | public static void main(String[] args) { MyJvmThread thread1 = new MyJvmThread(); MyJvmThread thread2 = new MyJvmThread(); thread1.start(); thread2.start(); } |
测试结果如下:多线程情况下就出现了问题,可以获取多个不同的对象
Thread-0-->创建:com.fz.Thread.syn.MyJvm@785d65
Thread-1-->创建:com.fz.Thread.syn.MyJvm@3bc257
3、给单例加锁:直接加在方法上
修改MyJvm这个单例类:直接在方法上加锁(效率低)
1 2 3 4 5 6 7 8 9 10 11 | //懒汉式的单例类 class MyJvm{ private static MyJvm instance = null ; private MyJvm(){} public static synchronized MyJvm getInstance(){ //这里在方法上直接加上synchronized if (instance == null ) { instance = new MyJvm(); } return instance; } } |
使用多线程的main方法测试:结果是正确的
Thread-1-->创建:com.fz.Thread.syn.MyJvm@2c1e6b
Thread-0-->创建:com.fz.Thread.syn.MyJvm@2c1e6b
虽然以上结果是正确的,但是存在效率问题。每个获取getInstance()方法的对象都需要锁等待。假如第一个线程进来发现instance为null就创建了,
等第二个线程进来的时候其实instance已经存在对象了,但是还是会进行锁等待。造成效率低。
4、将synchronized加在代码块中:
继续修改MyJvm这个类,将synchronized加在创建instance对象上
1 2 3 4 5 6 7 8 9 10 11 12 13 | //懒汉式的单例类 class MyJvm{ private static MyJvm instance = null ; private MyJvm(){} public static MyJvm getInstance(){ synchronized (MyJvm. class ){ ////静态方法里没有this,所以只能锁定类的字节码信息 if (instance == null ) { instance = new MyJvm(); } return instance; } } } |
如果就像上面这样把getInstance()方法中的全部代码都使用synchronized锁住的话,同样会有和第3步(给单例加锁:直接加在方法上)一样的效率低的问题。即使instance已经初始化了,后进来的线程还是会锁等待。
所以,继续改进。加入双重检索(double checking)的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //懒汉式的单例类 class MyJvm{ private static MyJvm instance = null ; private MyJvm(){} public static MyJvm getInstance(){ if (instance == null ) { //第1个线程进来会进入锁然后创建对象,第2个线程再走到这的时候已经不会再进入同步块不会出现锁等待了 synchronized (MyJvm. class ){ ////静态方法里没有this,所以只能锁定类的字节码信息 if (instance == null ) { instance = new MyJvm(); } } } return instance; } } |
最后再使用多线程main方法测试下:结果正确,并且效率会比上面的方法更快。
Thread-0-->创建:com.fz.Thread.syn.MyJvm@3bc257
Thread-1-->创建:com.fz.Thread.syn.MyJvm@3bc257
【这里可以在测试的时候加入开始时间和结束时间来更清楚的看到执行效率】
完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | public class SynDemo3 { public static void main(String[] args) { MyJvmThread thread1 = new MyJvmThread(); MyJvmThread thread2 = new MyJvmThread(); thread1.start(); thread2.start(); } } //懒汉式的单例类 class MyJvm{ private static MyJvm instance = null ; private MyJvm(){} //双重检索方式:效率高,第一次访问需要锁等待。之后的就直接获取了 public static MyJvm getInstance(){ if (instance == null ) { synchronized (MyJvm. class ){ //静态方法里没有this,所以只能锁定类的字节码信息 if (instance == null ) { instance = new MyJvm(); } } } return instance; } //synchronized代码块范围太大:效率低,每个访问的对象都需要进行锁等待 public static MyJvm getInstance3(){ synchronized (MyJvm. class ){ //静态方法里没有this,所以只能锁定类的字节码信息 if (instance == null ) { instance = new MyJvm(); } return instance; } } //将synchronized加在方法上:效率低,每个访问的对象都需要进行锁等待 public static synchronized MyJvm getInstance2(){ //这里在方法上直接加上synchronized if (instance == null ) { instance = new MyJvm(); } return instance; } //普通获取:线程不安全 public static MyJvm getInstance1(){ if (instance == null ) { instance = new MyJvm(); } return instance; } } //多线程类 class MyJvmThread extends Thread{ @Override public void run() { System.out.println(Thread.currentThread().getName()+ "-->创建:" +MyJvm.getInstance()); } } |