volatile引发的一个有趣的测试

本文探讨了volatile关键字在Java中的作用,通过实例演示了它如何防止CPU指令重排序,但不能确保基于volatile变量的运算在多线程环境中是安全的。文章提供了多个测试案例,展示了synchronized和AtomicInteger在实现线程安全方面的效果。

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

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

volatile 的意义?

  • 防止CPU指令重排序

volatile有两条关键的语义:

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

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

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


原测试示例

例子是这样的:

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 值又是多少呢 ?

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方法对应的线程 不会等待 新创建的线程执行完。

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

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++;
        }
    }
}

控制台打印的结果:

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); 看看:

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++;
        }
    }
}

查看控制台输出:

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 值是多少 ?

我们修改代码如下:

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++;
        }
    }
}

查看控制台输出:

[100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100]

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

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));
        }
    }
}

查看控制台输出:

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 次之后:

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++;
        }
    }
}

查看控制台输出:

[9337838, 9957329, 10000000, 10000000, 10000000, 9925170, 10000000, 9922369, 10000000, 10000000]

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

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++;
        }
    }
}

查看控制台输出:

[2292403, 2449807, 2146843, 1899033, 2120498, 4689152, 2264998, 2181451, 2266435, 2443323]

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


执行结果正确的代码

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

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++;
        }
    }
}

或者

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);
        }
    }
}

或者

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();
        }
    }
}

以上

<think>我们正在处理一个关于在Linux中使用mkdir命令创建临时易失性文件夹路径的问题。 注意:用户要求创建一个volatile”路径,这通常意味着该文件夹在系统重启后不会保留,即临时性。在Linux中,临时文件夹通常位于/tmp目录下,因为/tmp目录的内容在系统重启时会被清除(取决于系统配置,但通常如此)。 因此,我们可以建议用户在/tmp目录下创建文件夹,例如: mkdir /tmp/my_volatile_dir 但是,用户可能希望这个文件夹具有特定的名称,并且明确要求是易失性的(即临时性的)。 另外,我们也可以使用mktemp命令来创建一个临时目录,这样会生成一个随机名称的目录,以避免名称冲突。例如: mktemp -d 这个命令会在/tmp目录下创建一个临时目录,并输出该目录的路径。 如果用户想要指定一个特定的名称前缀,可以这样: mktemp -d -t myprefix.XXXXXX 所以,我们可以提供两种方法。 根据引用[1]中关于绝对路径的说明,我们创建的是绝对路径。 同时,注意引用[1]中提到了相对路径和绝对路径,这里我们创建的是绝对路径(因为/tmp是绝对路径)。 因此,回答如下:</think>在 Linux 中创建临时易失性文件夹路径(系统重启后自动消失)的最佳实践是使用 `/tmp` 目录,因为该目录在大多数 Linux 系统中默认配置为**临时文件系统**(tmpfs),其内容会随系统重启完全清空[^1]。 ### 推荐方法 1. **使用 `/tmp` 目录创建临时文件夹** 直接创建: ```bash mkdir /tmp/my_volatile_folder ``` - 路径示例:`/tmp/my_volatile_folder` - 特性:系统重启后自动消失,适合存储临时数据 2. **使用 `mktemp` 命令(推荐)** 自动生成唯一路径: ```bash volatile_path=$(mktemp -d) echo "临时路径:$volatile_path" # 例如输出 /tmp/tmp.ABCD1234 ``` - 优势:避免名称冲突,自动生成随机目录名 - 验证易失性:重启后路径自动消失 ### 验证易失性 ```bash # 创建后检查 ls $volatile_path # 重启系统后再次检查 ls $volatile_path # 报错:No such file or directory ``` ### 注意事项 1. **权限问题** `/tmp` 目录通常对所有用户可写,但创建的文件夹权限默认为 `755`(所有者完全控制)。如需限制访问: ```bash mkdir /tmp/private_tmp && chmod 700 /tmp/private_tmp ``` 2. **系统差异** - 某些系统可能将 `/tmp` 挂载到磁盘而非内存(可通过 `df -Th /tmp` 查看) - 若挂载类型为 `tmpfs`(如输出 `tmpfs tmpfs ...`),则确认为易失性存储[^1] 3. **替代方案** 若需内存级易失性(非磁盘),可挂载 `tmpfs`: ```bash sudo mkdir /mnt/ramdisk sudo mount -t tmpfs -o size=512M tmpfs /mnt/ramdisk ``` - 此路径数据仅存于内存,重启后消失(需 root 权限)[^4] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值