前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >volatile引发的一个有趣的测试

volatile引发的一个有趣的测试

作者头像
103style
发布2022-12-19 13:51:57
1940
发布2022-12-19 13:51:57
举报
文章被收录于专栏:Android开发经验分享

转载请以链接形式标明出处: 本文出自:103style的博客

本文是 看到 这篇文章中 “volatile 的意义?” 那一小节提供的一个例子引发的测试。

volatile 的意义?

  • 防止CPU指令重排序

volatile有两条关键的语义:

  • 保证被volatile修饰的变量对所有线程都是可见的
  • 禁止进行指令重排序

下面的例子是用来证明下面这个观点的后半句是错误的。

由于volatile修饰的变量在各个线程里都是一致的,所以基于volatile变量的运算在多线程并发的情况下是安全的。


原测试示例

例子是这样的:

代码语言:javascript
复制
public class Test {
    private volatile int start = 0;
    public static void main(String[] args) {
        new Test().test();
    }
    private void test() {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                add();
            }
        };
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(runnable);
            thread.start();
        }
        System.out.println("start = " + start);
    }
    void add() {
        for (int i = 0; i < 10; i++) {
            start++;
        }
    }
}

大家可以看看 最后运行的结果输出的 start 值是多少 ?


给 add 方法加上 synchronized 之后

add() 方法加上 synchronized 之后输出的 start 值又是多少呢 ?

代码语言:javascript
复制
public class Test {
    private volatile int start = 0;
    public static void main(String[] args) {
        new Test().test();
    }
    private void test() {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                add();
            }
        };
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(runnable);
            thread.start();
        }
        System.out.println("start = " + start);
    }
    synchronized void add() {
        for (int i = 0; i < 10; i++) {
            start++;
        }
    }
}

两个测试的结果

两个结果都是 100? 还是只有第二个是100 ?

其实两个结果都不是100,这是因为 main方法对应的线程 不会等待 新创建的线程执行完。

我们可以加上时间输出看看试试:

代码语言:javascript
复制
public class Test {
    private volatile int start = 0;
    public static void main(String[] args) {
        new Test().test();
    }
    private void test() {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                add();
                System.out.println("new thread: " + System.currentTimeMillis());
            }
        };
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(runnable);
            thread.start();
        }
        System.out.println("start = " + start);
        System.out.println("main: " + System.currentTimeMillis());
    }
    void add() {
        for (int i = 0; i < 10; i++) {
            start++;
        }
    }
}

控制台打印的结果:

代码语言:javascript
复制
new thread: 1583915204005
new thread: 1583915204005
new thread: 1583915204007
new thread: 1583915204007
new thread: 1583915204007
new thread: 1583915204007
start = 60
main: 1583915204007
new thread: 1583915204008
new thread: 1583915204008
new thread: 1583915204008
new thread: 1583915204008

可以看到 main方法对应的线程 先执行完了。

然后为了解决 main方法对应的线程 先执行完, 我们加上 Thread.sleep(1000); 看看:

代码语言:javascript
复制
public class Test {
    private volatile int start = 0;
    public static void main(String[] args) {
        new Test().test();
    }
    private void test() {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                add();
                System.out.println("new thread: " + System.currentTimeMillis());
            }
        };
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(runnable);
            thread.start();
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("start = " + start);
        System.out.println("main: " + System.currentTimeMillis());
    }
    void add() {
        for (int i = 0; i < 10; i++) {
            start++;
        }
    }
}

查看控制台输出:

代码语言:javascript
复制
new thread: 1583915390819
new thread: 1583915390821
new thread: 1583915390822
new thread: 1583915390823
new thread: 1583915390823
new thread: 1583915390823
new thread: 1583915390823
new thread: 1583915390823
new thread: 1583915390823
new thread: 1583915390823
start = 100
main: 1583915391822

去掉修饰 start 的 volatile 修饰符

然后我们试试把 volatile 修饰符 去掉试试,运行的结果输出的 start 值是多少 ?

我们修改代码如下:

代码语言:javascript
复制
public class Test {
    private int start = 0;
    public static void main(String[] args) {
        List<Integer> res = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            res.add(new Test().test());
        }
        System.out.println(res);
    }
    private int test() {
        Runnable runnable = () -> add();
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(runnable);
            thread.start();
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return start;
    }
    void add() {
        for (int i = 0; i < 10; i++) {
            start++;
        }
    }
}

查看控制台输出:

代码语言:javascript
复制
[100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100]

就不卖关子了,这里结果没问题的原因主要是因为 创建线程的耗时 比 add 方法执行的耗时还长, 所以就相当与单线程执行了,我们可以来验证下。

代码语言:javascript
复制
public class Test {
    private int start = 0;
    public static void main(String[] args) {
        new Test().test();
    }
    private int test() {
        for (int i = 0; i < 10; i++) {
            long t = System.nanoTime();
            Thread thread = new Thread(new TestRunnable(i));
            System.out.println(i + "_new_thred: " + (System.nanoTime() - t));
            thread.start();
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return start;
    }
    void add() {
        for (int i = 0; i < 10; i++) {
            start++;
        }
    }
    private class TestRunnable implements Runnable {
        int id;
        public TestRunnable(int i) {
            id = i;
        }
        @Override
        public void run() {
            long t = System.nanoTime();
            add();
            System.out.println(id + "_add: " + (System.nanoTime() - t));
        }
    }
}

查看控制台输出:

代码语言:javascript
复制
0_new_thred: 1232700
1_new_thred: 31800
2_new_thred: 18000
3_new_thred: 24500
0_add: 62100
4_new_thred: 19700
5_new_thred: 76800
3_add: 19200
6_new_thred: 22300
7_new_thred: 24500
8_new_thred: 32000
9_new_thred: 26100
4_add: 23100
8_add: 20000
7_add: 18400
1_add: 20900
2_add: 19600
5_add: 40300
9_add: 22100
6_add: 23600

当我们修改 add 方法的次数为 10W 次之后:

代码语言:javascript
复制
public class Test {
    private int start = 0;
    public static void main(String[] args) {
        List<Integer> res = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            res.add(new Test().test());
        }
        System.out.println(res);
    }
    private int test() {
        Runnable runnable = () -> add();
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        for (int i = 0; i < 100; i++) {
            executorService.execute(runnable);
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();
        return start;
    }
    void add() {
        for (int i = 0; i < 100000; i++) {
            start++;
        }
    }
}

查看控制台输出:

代码语言:javascript
复制
[9337838, 9957329, 10000000, 10000000, 10000000, 9925170, 10000000, 9922369, 10000000, 10000000]

修改上面的测试代码,给 start 添加 volatile 修饰符:

代码语言:javascript
复制
public class Test {
    private volatile int start = 0;
    public static void main(String[] args) {
        List<Integer> res = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            res.add(new Test().test());
        }
        System.out.println(res);
    }
    private int test() {
        Runnable runnable = () -> add();
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        for (int i = 0; i < 100; i++) {
            executorService.execute(runnable);
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();
        return start;
    }
    void add() {
        for (int i = 0; i < 100000; i++) {
            start++;
        }
    }
}

查看控制台输出:

代码语言:javascript
复制
[2292403, 2449807, 2146843, 1899033, 2120498, 4689152, 2264998, 2181451, 2266435, 2443323]

可以发现结果也是不对的。


执行结果正确的代码

要正确输出结果我们可以修改代码如下:

代码语言:javascript
复制
public class Test {
    private int start = 0;
    public static void main(String[] args) {
        List<Integer> res = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            res.add(new Test().test());
        }
        System.out.println(res);
    }

    private int test() {
        Runnable runnable = () -> add();
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 100; i++) {
            executorService.execute(runnable);
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();
        return start;
    }
    synchronized void add() {
        for (int i = 0; i < 100000; i++) {
            start++;
        }
    }
}

或者

代码语言:javascript
复制
public class Test {
    private AtomicInteger start = new AtomicInteger(0);
    public static void main(String[] args) {
        List<Integer> res = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            res.add(new Test().test());
        }
        System.out.println(res);
    }
    private int test() {
        Runnable runnable = () -> add();
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 100; i++) {
            executorService.execute(runnable);
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();
        return start.get();
    }
    void add() {
        for (int i = 0; i < 100000; i++) {
            start.addAndGet(1);
        }
    }
}

或者

代码语言:javascript
复制
public class Test {
    private static ReentrantLock lock = new ReentrantLock();
    private int start = 0;

    public static void main(String[] args) {
        List<Integer> res = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            res.add(new Test().test());
        }
        System.out.println(res);
    }

    private int test() {
        Runnable runnable = () -> add();
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 100; i++) {
            executorService.execute(runnable);
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();
        return start;
    }

    void add() {
        lock.lock();
        try {
            for (int i = 0; i < 100000; i++) {
                start++;
            }
        } finally {
            lock.unlock();
        }
    }
}

以上

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-03-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 原测试示例
  • 给 add 方法加上 synchronized 之后
  • 两个测试的结果
  • 去掉修饰 start 的 volatile 修饰符
  • 执行结果正确的代码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档