互联网技术04——volatile关键字

思考:

1.当多个线程同时访问同一个变量时,为了避免线程问题,我们可以给其加上synchronized关键字。但是每个线程都需要等待,这就造成并发数的太低的问题。

看下面代码:

package com.company;

/**
 * Created by BaiTianShi on 2018/8/14.
 */
public class VolatileTest extends Thread{
    private boolean isRunning = true;

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

    @Override
    public void run() {
        while (isRunning){
            //
        }
        System.out.println("run执行结束");
    }

    public static void main(String[] args) throws InterruptedException {
        VolatileTest t = new VolatileTest();
        t.start();
        Thread.sleep(3000);

        t.setValue(false);
        System.out.println("已经设置了false值");
        Thread.sleep(1000);
        System.out.println(t.isRunning);
    }
}

执行结果

已经设置了false值
false

虽然已经设置了false给isRuning,并且已经打印出了isRuning的值为false,但是run方法仍然没有跳出while循环,这就说明run方法不知道isRunNing值已经改变了。

原因分析:

线程在执行过程中,如果每次都去主内存中去取变量值,会造成很大的性能开销(内存资源宝贵)。为了避免这种情况,引入了线程工作内存机制。线程工作内存中存储一份需要isRuning的副本,虽然主内存中的isRuning已经被设置为false,但线程工作内存中的isRuning仍然是true。

为了避免这种情况的发生,我们可以给isRuning加上volatitle关键字修饰。被修饰的变量,当主内存中的数据改变时,会通知线程工作内存,强制其读取isRuning值时,去主内存中获取,并刷新线程内存中的isRuning值。

由于只是给isRuning加volatitle修饰,运行代码省略,这里我就只展示运行结果了

已经设置了false值
run执行结束
false

原子性测试

volatitle修饰变量不具有原子性,也就是说多个线程同时修改时,可能造成数据错误

package com.company;

/**
 * Created by BaiTianShi on 2018/8/14.
 */
public class AtomicTest extends Thread {
    private volatile static int count;

    private void addCount(){
        for(int i=0;i<1000;i++){
            count++;
        }
        System.out.println(count);
    }

    @Override
    public void run() {
        addCount();
    }

    public static void main(String[] args) {
        for(int i=0;i<10;i++){
            AtomicTest at = new AtomicTest();
            at.start();
        }
    }
}

运行结果

1000
4000
3000
2000
5000
6000
7000
8000
9050
9757

理想状态下是没执行一次addCount方法,count的值应该增加1000,循环十次,最后的结果应该是10000,但是显然不符合我们的预期,所以volatitle不符合原子性(原子是最小的单位,引申意义就是计算不能被拆分)。

解决办法

如果想让变量具有原子性,可以声明Atomicxxx来代替,如上段代码中我们将count声明为AtomicInteger类型

package com.company;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by BaiTianShi on 2018/8/14.
 */
public class AtomicTest extends Thread {
//    private volatile static int count =0;

    private volatile static AtomicInteger count = new AtomicInteger(0);

    private void addCount(){
        for(int i=0;i<1000;i++){
//            count++;
            count.incrementAndGet();
        }
        System.out.println(count);
    }

    @Override
    public void run() {
        addCount();
    }

    public static void main(String[] args) {
        for(int i=0;i<10;i++){
            AtomicTest at = new AtomicTest();
            at.start();
        }
    }
}

运行结果(一种情况):

3120
3120
3120
4000
6000
5000
7000
8000
9000
10000

可见最后的结果是对的,也就是说过程不具有原子性,但是结果具有原子性,这再一定程度上我们是可以接受的。

        volatile关键字虽然拥有多个线程之间的可见性,但是却不具备同步性(也就是原子性),可以算上是一个轻量级的synchronized,性能要比synchronized强很多,不会造成阻塞(在很多开源的架构里,比如netty的底层代码就大量使用volatile,可见netty性能一定是非常不错的。)这里需要注意:一般volatile用于只针对于多个线程可见的变量操作,并不能代替synchronized的同步功能。实现原子性建议使用atomic类的系列对象,支持原子性操作(注意atomic类只保证本身方法原子性,并不保证多次操作的原子性)

多次操作演示代码

package com.company;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by BaiTianShi on 2018/8/14.
 */
public class AtomicManyAdd extends Thread {

    private volatile AtomicInteger count = new AtomicInteger(0);
    @Override
    public void run() {

    }
    public int addCount () throws InterruptedException {
        Thread.sleep(100);
        count.addAndGet(1);
        count.addAndGet(2);
        count.addAndGet(3);
        count.addAndGet(4);
        return  count.get();
    }

    public static void main(String[] args){
        final AtomicManyAdd au = new AtomicManyAdd();
        List<Thread> ts = new ArrayList<Thread>();
        for(int i=0;i<100;i++){
            ts.add(new Thread(new Runnable() {

                @Override
                public void run() {
                    try {
                        System.out.println(au.addCount());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }));
        }
        for(Thread t:ts){
            t.start();
        }
    }

}

运行结果:

D:\E\devtools\jdk1.8.0_77\bin\java -Didea.launcher.port=7533 -Didea.launcher.bin.path=D:\E\devtools\IdeaProjects\IntelliJIDEA\bin -Dfile.encoding=UTF-8 -classpath D:\E\devtools\jdk1.8.0_77\jre\lib\charsets.jar;D:\E\devtools\jdk1.8.0_77\jre\lib\deploy.jar;D:\E\devtools\jdk1.8.0_77\jre\lib\ext\access-bridge-64.jar;D:\E\devtools\jdk1.8.0_77\jre\lib\ext\cldrdata.jar;D:\E\devtools\jdk1.8.0_77\jre\lib\ext\dnsns.jar;D:\E\devtools\jdk1.8.0_77\jre\lib\ext\jaccess.jar;D:\E\devtools\jdk1.8.0_77\jre\lib\ext\jfxrt.jar;D:\E\devtools\jdk1.8.0_77\jre\lib\ext\localedata.jar;D:\E\devtools\jdk1.8.0_77\jre\lib\ext\nashorn.jar;D:\E\devtools\jdk1.8.0_77\jre\lib\ext\sunec.jar;D:\E\devtools\jdk1.8.0_77\jre\lib\ext\sunjce_provider.jar;D:\E\devtools\jdk1.8.0_77\jre\lib\ext\sunmscapi.jar;D:\E\devtools\jdk1.8.0_77\jre\lib\ext\sunpkcs11.jar;D:\E\devtools\jdk1.8.0_77\jre\lib\ext\zipfs.jar;D:\E\devtools\jdk1.8.0_77\jre\lib\javaws.jar;D:\E\devtools\jdk1.8.0_77\jre\lib\jce.jar;D:\E\devtools\jdk1.8.0_77\jre\lib\jfr.jar;D:\E\devtools\jdk1.8.0_77\jre\lib\jfxswt.jar;D:\E\devtools\jdk1.8.0_77\jre\lib\jsse.jar;D:\E\devtools\jdk1.8.0_77\jre\lib\management-agent.jar;D:\E\devtools\jdk1.8.0_77\jre\lib\plugin.jar;D:\E\devtools\jdk1.8.0_77\jre\lib\resources.jar;D:\E\devtools\jdk1.8.0_77\jre\lib\rt.jar;D:\E\devtools\projects\Thread\out\production\Thread;D:\E\devtools\IdeaProjects\IntelliJIDEA\lib\idea_rt.jar com.intellij.rt.execution.application.AppMain com.company.AtomicManyAdd
70
80
46
50
60
42
36
120
50
150
130
140
100
90
110
180
210
250
170
160
260
240
230
220
200
310
190
330
320
350
300
290
360
390
280
270
446
450
420
430
400
410
380
530
373
340
680
740
720
730
710
690
700
780
830
673
653
663
633
643
623
590
613
880
960
600
590
570
560
550
540
1000
520
500
510
490
480
470
460
980
980
990
940
950
930
920
910
900
890
870
860
850
840
820
810
790
800
770
750
760

Process finished with exit code 0

可见多次出现非10的整数倍的数值,说明aomic不能保证多次操作的原子性

如果我们要保证multiAdd方法的原子性的话,我们就给multiAdd方法添加synchronized关键字

猜你喜欢

转载自blog.csdn.net/qq_28240551/article/details/81639770