Java关键字volatile在多线程环境下的用途

范例1:首先看一个子线程的死循环的错误范例

package com.contoso;

public class App {

    public static void main(String[] args) {
        try {
            ChildThread childThread = new ChildThread();
            childThread.start();
            Thread.sleep(4); // 这个毫秒数决定子线程的while循环内部代码是否执行
            childThread.setRunning(false);
            System.out.println(Thread.currentThread().getName() + "主线程已给" + childThread.getName() + "子线程运行状态isRunning赋值为false");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
package com.contoso;

public class ChildThread extends Thread {

    private boolean isRunning = true;
    private static int count = 0;

    public void setRunning(boolean isRunning) {
        this.isRunning = isRunning;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "子线程已进入重写的run()方法");
        try {
            while (isRunning == true) {
                count = count + 1;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("count = " + count);
        System.out.println(Thread.currentThread().getName() + "子线程的run()方法最后一行代码已运行完毕准备结束线程");
    }
}

运行以上范例控制台会打印输出如下截图信息:

上面截图中这个是错误的死循环输出结果

我们将主线程睡眠时间改成3毫秒,以上范例输出结果就更不稳定啦

可以试试看,多运行几次以上范例代码,除了子线程也会像上面那样进入死循环这种情况之外,

还可能会打印如下信息:

run:
Thread-0子线程已进入重写的run()方法
count = 724563
main主线程已给Thread-0子线程运行状态isRunning赋值为false
Thread-0子线程的run()方法最后一行代码已运行完毕准备结束线程
BUILD SUCCESSFUL (total time: 0 seconds)

还可能打印如下信息:

run:
Thread-0子线程已进入重写的run()方法
count = 120204
main主线程已给Thread-0子线程运行状态isRunning赋值为false
Thread-0子线程的run()方法最后一行代码已运行完毕准备结束线程
BUILD SUCCESSFUL (total time: 0 seconds)

即使主线程睡眠时间改成7毫秒也会出现上面的多种输出结果(包括死循环,运行100次范例可能90次是死循环)

正确的代码只需要将子线程中相应的代码改成 private volatile boolean isRunning = true;

也就是加一个修饰变量的关键字volatile就能搞定!

我们接下来分析一下出现这种情况的原因

当启动子线程ChildThread时,变量isRunning的默认值等于true
变量isRunning存储在两个地方,一个位置在公共堆栈中,
另一个位置在线程的私有堆栈中,子线程一直在私有堆栈中获得
isRunning的变量的值true,主线程中执行childThread.setRunning(false)代码
行更新的却是公共堆栈中isRunning变量的值,即使公共堆栈中的isRunning变量的值为false,
子线程一直依然获得是私有堆栈中isRunning变量的值true,所以while循环就会出现死循环

在子线程中,给变量isRunning加一个volatile关键字,
它就会让子线程直接从公共堆栈中获得isRunning变量的值false,
这样正在运行的while循环体就会退出循环,从而保证了代码逻辑上正确性!

范例2:使用线程打印素数

package com.contoso;

import java.math.BigInteger;

public class MainApp implements Runnable {

    private volatile boolean isRunning = true;

    @Override
    public void run() {
        BigInteger p = BigInteger.ONE;
        while (isRunning) {
            synchronized (this) {
                p = p.nextProbablePrime();
                System.out.println("素数:" + p);
            }
        }
    }

    public void setRunning(boolean isRunning) {
        this.isRunning = isRunning;
    }

    public static void main(String[] args) throws InterruptedException {
        MainApp app = new MainApp();
        new Thread(app).start();
        try {
            Thread.sleep(50);
        } finally {
            app.setRunning(false);
        }
    }

}
run:
素数:2
素数:3
素数:5
素数:7
素数:11
素数:13
素数:17
素数:19
素数:23
素数:29
素数:31
素数:37
素数:41
素数:43
素数:47
素数:53
素数:59
素数:61
素数:67
素数:71
素数:73
素数:79
素数:83
素数:89
素数:97
素数:101
素数:103
素数:107
素数:109
素数:113
素数:127
素数:131
素数:137
素数:139
素数:149
素数:151
素数:157
素数:163
素数:167
素数:173
素数:179
素数:181
素数:191
素数:193
素数:197
素数:199
素数:211
素数:223
素数:227
素数:229
素数:233
素数:239
素数:241
素数:251
素数:257
素数:263
素数:269
素数:271
素数:277
素数:281
素数:283
素数:293
素数:307
素数:311
素数:313
素数:317
素数:331
素数:337
素数:347
素数:349
素数:353
素数:359
素数:367
素数:373
素数:379
素数:383
素数:389
素数:397
素数:401
素数:409
素数:419
素数:421
素数:431
素数:433
素数:439
素数:443
素数:449
素数:457
素数:461
素数:463
素数:467
素数:479
素数:487
素数:491
素数:499
素数:503
素数:509
素数:521
素数:523
素数:541
素数:547
素数:557
素数:563
素数:569
素数:571
素数:577
素数:587
素数:593
素数:599
素数:601
素数:607
素数:613
素数:617
素数:619
素数:631
素数:641
素数:643
素数:647
素数:653
素数:659
素数:661
素数:673
素数:677
素数:683
素数:691
素数:701
素数:709
素数:719
素数:727
素数:733
BUILD SUCCESSFUL (total time: 0 seconds)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值