单例是较为常见的设计模式,在实现延迟加载时,会出现线程安全的问题,我们一般采用加锁的方式,不采用加锁的方式例如枚举,或者非延迟加载的方式之类的的最终虚拟机在执行的时候会帮我们加锁。那么如何实现一个不加锁的单例呢? 可以借助cas来实现
先看下不安全的情况
class Single{ private static Single single; private static AtomicReference<Single> sing = new AtomicReference(); private Single(){ Integer i = 0; while ( i++ > 10000 ); }; private static Single getInstance(){ /********不安全********/ if ( single == null ){ single = new Single(); return single; } return single; /********安全********/ // if ( sing.get() != null ){ // return sing.get(); // } // sing.compareAndSet(null,new Single()); // return sing.get(); } public static void main(String[] args) throws InterruptedException { CyclicBarrier cyclicBarrier = new CyclicBarrier(500); CountDownLatch countDownLatch = new CountDownLatch(500); final Map<Single,String> map = new ConcurrentHashMap<>(); ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 500; i++) { executorService.execute(()->{ try { cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } Single instance = Single.getInstance(); map.put(instance,"1"); countDownLatch.countDown(); }); } executorService.shutdown(); countDownLatch.await();
System.out.println(String.format("共初始化 %d 个,分别为 %s",map.keySet().size(),map.keySet()));
} }
为了更加容易模拟,我在初始化中加入了稍微费时点儿的操作。并且借助CyclicBarrier 来模拟多个线程同一时间请求。这样在不安全的情况下,得到的结果会可能如下有多个
然后我们把不安全的步骤注释掉,执行安全的步骤时,多次试验下均只有一个
private static Single getInstance(){ /********不安全********/ // if ( single == null ){ // single = new Single(); // return single; // } // return single; /********安全********/ if ( sing.get() != null ){ return sing.get(); }
//只有一个线程会成功 sing.compareAndSet(null,new Single()); return sing.get(); }
结果如下