作为一个基础知识,java 下的一种单例实现是这样的
package me.zrj.test.test20170607; public class Singleton { private Singleton() {} private static Singleton instance; public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
这种单例的写法的明显问题就是线程安全,说都知道这么说,那么怎么来复现这个线程安全问题呢
一个最直接的写法就是这样
package me.zrj.test.test20170607; public class MyThread implements Runnable { @Override public void run() { System.out.println(Singleton.getInstance()); } }
package me.zrj.test.test20170607; public class App { public static void main( String[] args ) throws InterruptedException { System.out.println( "Hello World!" ); new Thread(new MyThread()).start(); new Thread(new MyThread()).start(); } }
但是这个复现的验证代码本身也是能看出来是有问题的,就是 new Thread 的先后 start 并不能保证他们在“同一时间”开始跑,而如果他们先后开始跑的时刻相差比较大,那么就无法复现那个单例的实现的 bug 了
但是。。。实际上验证的时候发现,这种写法几乎每次都能复现单例实现的 bug,由此可见,相比于 new 一个对象的耗时来说,new 一个线程的耗时还是更短的
那如果我们把这个验证的代码写成这样子呢
package me.zrj.test.test20170607; public class App { public static void main( String[] args ) throws InterruptedException { System.out.println( "Hello World!" ); new Thread(new MyThread()).start(); Thread.sleep(100); new Thread(new MyThread()).start(); } }
这样的话,就可以看到两次拿回来的单例都是指向同一个地址了
那么,如果在这种情况下,我们还想能够复现那个单例实现的 bug,应该怎么来做呢,或者说,怎么来实现一个 countdownlatch 呢
按照理解去写,可以写成这样
package me.zrj.test.test20170607; public class MyThread implements Runnable { synchronized void print() { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "\t" + Singleton.getInstance()); } synchronized void go() { notifyAll(); } @Override public void run() { print(); } }
package me.zrj.test.test20170607; import java.util.Date; public class App { public static void main( String[] args ) throws InterruptedException { System.out.println( "Hello World!" ); MyThread myThread = new MyThread(); new Thread(myThread).start(); Thread.sleep(100); new Thread(myThread).start(); myThread.go(); while (true) { Thread.sleep(1000); System.out.println(new Date()); } } }
但是发现这种写法会出现一个问题,就是虽然 notifyAll() 了,但是依然只有一个线程打印出来东西
看到这里, https://www.zhihu.com/question… 说 notifyAll() 只是让所有线程去抢锁,但是其实还是只有一个线程能跑 synchronized 中的代码
所以真正的 countdownlatch 应该是这么来写
package me.zrj.test.test20170607; import java.util.concurrent.CountDownLatch; public class MyThread implements Runnable { CountDownLatch countDownLatch; public MyThread(CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; } synchronized void print() { try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " " + Singleton.getInstance()); } @Override public void run() { print(); } }
package me.zrj.test.test20170607; import java.util.Date; import java.util.concurrent.CountDownLatch; public class App { public static void main( String[] args ) throws InterruptedException { System.out.println( "Hello World!" ); CountDownLatch countDownLatch = new CountDownLatch(1); MyThread myThread = new MyThread(countDownLatch); new Thread(myThread).start(); Thread.sleep(100); new Thread(myThread).start(); countDownLatch.countDown(); while (true) { Thread.sleep(1000); System.out.println(new Date()); } } }