通过给getInstance()方法加上synchronized来解决线程一致性问题,结果分析虽然显示所有实例的hashcode都一致,但是synchronized的粒度太大了,即锁的临界区太大了,有点影响效率,例如如果第4行和第5行之间有业务处理逻辑,不会涉及共享变量,那么每次对这部分业务逻辑加锁必然会导致效率低下。为了解决粗粒度的问题,可以对代码进一步改进:
改进2 public class Singleton { private static Singleton singleton = null; private Singleton(){} public static Singleton getInstance(){ /* 一堆业务处理代码 */ if(null == singleton){ synchronized(Singleton.class){//锁粒度变小 try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } singleton = new Singleton(); } } return singleton; } public static void main(String[] args) { for (int i=0;i<1000;i++){ new Thread(()-> System.out.println(Singleton.getInstance().hashCode())).start(); } } }部分运行结果 :
391918859 391918859 391918859 1945023194通过分析运行结果发现,虽然锁的粒度变小了,但线程不安全了。为什么会这样呢?因为有种情况,线程1执行完if判断后还没有拿到锁的时候时间片用完了,此时线程2来了,执行if判断时发现对象还是空的,继续往下执行,很顺利的拿到锁了,因此线程2创建了一个对象,当线程2创建完之后释放掉锁,这时线程1激活了,顺利的拿到锁,又创建了一个对象。所以代码还需要再一步的改进。
改进3 public class Singleton { private static Singleton singleton = null; private Singleton(){} public static Singleton getInstance(){ /* 一堆业务处理代码 */ if(null == singleton){ synchronized(Singleton.class){//锁粒度变小 if(null == singleton){//DCL try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } singleton = new Singleton(); } } } return singleton; } public static void main(String[] args) { for (int i=0;i<1000;i++){ new Thread(()-> System.out.println(Singleton.getInstance().hashCode())).start(); } } }