newcoderlife

System.out.println 的线程安全问题
有锁的 System.out.println今天,我在实验“多线程循环输出 helloworld”的时候,发现“H...
扫描右侧二维码阅读全文
16
2019/07

System.out.println 的线程安全问题

有锁的 System.out.println

今天,我在实验“多线程循环输出 helloworld”的时候,发现“Hello,world!”在 Terminal 中的输出不会被乱序插入。而是这样:

可以看到,下面的“Hello,world!”排列整齐。于是,通过查阅源码,我发现 println 方法是有锁的:

System.out.println 带来的伪原子性

锁粗化

对于如下代码,我们都知道,volatile 只能防止指令重排序和保证内存可见,不能确保变量的原子性:

public class Test {
    private static final int THREADS_COUNT = 20;
    public static volatile int count = 0;

    public static void increase() {
        count++;
    }

    public static void main(String[] args) {
        Thread[] threads = new Thread[THREADS_COUNT];
        for (int i = 0; i < THREADS_COUNT; i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10000; i++) {
                        increase();
                    }
                }
            });
            threads[i].start();
        }
        while (Thread.activeCount() > 1) {
            Thread.yield();
        }
        System.out.println(count);
    }
}

所以,count 变量最后不会达到 200000.

但是如果这样写:

public class MultiThreadIncrease {
    static volatile int count = 0;

    public static void main(String[] args) throws InterruptedException {}

    static void increase() {
        count++;
        System.out.println(0);
    }
}

最后的输出将会是 200000。解释:对于处在循环内的 sychronized,Java 的锁粗化机制会把它优化为:

/* 优化前 */
for(i) {
  sychonized (obj) {}
}

/* 优化后 */
sychonized (obj) {
  for(i);
}

内存同步

对于这个:

public class MultiThreadBoolean implements Runnable {

    public volatile boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        MultiThreadBoolean runnable = new MultiThreadBoolean();
        new Thread(runnable).start();
        Thread.sleep(3000);
        runnable.flag = false;
        Thread.sleep(3000);
        System.out.println(runnable.flag);
    }

    @Override
    public void run() {
        while (flag) {
        }
    }
}

大家都知道因为 flag 的缓存没能和内存实时同步,所以造成 System.out.println(runnable.flag); 之后工作线程仍在运行的问题。但是如果这样:

while (flag) {
  System.out.println("1");
}

工作线程就会如期退出。解释:println 在每次输出后都会清楚工作线程的缓存,造成工作线程的内存同步。

Last modification:July 16th, 2019 at 06:11 pm
点击广告投喂博主 以获得更快的访问速度!

2 comments

  1. Dxoca

    一脸懵B

    1. newcoderlife
      @Dxoca

      《深入理解 Java 虚拟机》那本书上用了几页来讲这个,如果不写 Java 也可以不用关注。

Leave a Comment