作为一个基础知识,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());
}
}
}