Skip to content

令人迷惑的volatile例子(三) #12

Open
@seaswalker

Description

@seaswalker

锁还是打印

#11 的基础上,把代码再改写为:

package test;
class MultiProcessorTask {

    private boolean flag = true;

    public void runMethod() {
        while (flag) {
            System.out.println(1);
        }
    }

    public void stopMethod() throws InterruptedException {
        System.out.println("准备睡眠1秒,然后置flag为false.");
        Thread.sleep(1000);
        System.out.println("change 'flag' field ...");
        flag = false;
    }
}
class ThreadA extends Thread {

    private MultiProcessorTask task;

    ThreadA(MultiProcessorTask task) {this.task = task;}

    @Override
    public void run() {
        task.runMethod();
    }
}
public class TestRun {
    public static void main(String[] args) throws InterruptedException {
        MultiProcessorTask task = new MultiProcessorTask();
        ThreadA a = new ThreadA(task);
        a.start();
        task.stopMethod();
        System.out.println("it's over");
    }
}

这样也能正常退出,众所周知,打印里面是有锁的,所以这里是打印还是锁阻止了JIT激进优化?不知道,对JIT优化的过程了解很少,我赌五毛是打印。

定时更新,定时读取

package test;

public class TestRun {

    private static int version = 0;

    private static class Writer implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(50);
                    version += 1;
                    System.out.println("Writer更新为: " + version + ", 时间: " + System.currentTimeMillis());
                } catch (InterruptedException ignore) {
                }
            }
        }
    }

    private static class Reader implements Runnable {

        private final int index;
        private final long sleepTime;

        private Reader(int index, long sleepTime) {
            this.index = index;
            this.sleepTime = sleepTime * 10;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(sleepTime);
                    System.out.println("Read" + index + "读到: " + version + ", 时间: " + System.currentTimeMillis());
                } catch (InterruptedException ignore) {
                }
            }
        }
    }

    public static void main(String[] args) {
        new Thread(new Reader(1, 3)).start();
        new Thread(new Reader(2, 4)).start();
        new Thread(new Writer()).start();
    }
}

一个写线程每隔50毫秒把version加一,两个读线程分别每隔30毫秒和40毫秒读一次version值,测试执行了147089次,触发了最高级别的C2/OSR/Level 4编译,在这种情况下仍可以保证Reader线程读到最新值,如下:

Writer更新为: 147087, 时间: 1593965931595
Read1读到: 147087, 时间: 1593965931603
Read2读到: 147087, 时间: 1593965931612
Read1读到: 147087, 时间: 1593965931635
Writer更新为: 147088, 时间: 1593965931646
Read2读到: 147088, 时间: 1593965931656
Read1读到: 147088, 时间: 1593965931665
Read1读到: 147088, 时间: 1593965931698
Writer更新为: 147089, 时间: 1593965931698
Read2读到: 147089, 时间: 1593965931698
Read1读到: 147089, 时间: 1593965931733
Read2读到: 147089, 时间: 1593965931739

编译级别:
image
image
至此彻底说明了,对于单个变量的并发读写在硬件层面上根本无需同步,给我们造成"不可见"现象的真正原因是JIT的激进优化。当然,JMM规范还是应当遵守的,毕竟不能保证所在的场景就一定不会被JIT做些手脚。这几个例子的用意是说清楚长久以来在🧠里的一些混乱。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions