前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java多线程(下)

Java多线程(下)

作者头像
润森
发布2019-11-09 17:10:12
3990
发布2019-11-09 17:10:12
举报
文章被收录于专栏:毛利学Python

学习一时爽,一直学习一直爽

  Hello,大家好,我是 もうり,一个从无到有的技术+语言小白。

多线程混乱

在 Java 中多线程永远是一个重要的话题

多线程混乱造成 线程不安全

线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。

回顾 synchronized 关键字
  • DateHolder,java
代码语言:javascript
复制
/** * @author: 毛利 */public class DateHolder {    private long number = 0;    public void change(long delta) {        number += delta;    }    public void print() {        System.out.println("Number=" + number);    }}
  • ChangeData,java
代码语言:javascript
复制
/** * @author: 毛利 */public class ChangeData implements Runnable{    // 私有对象DataHolder    private DataHolder dataHolder;    private long loopCount;    private long delta;    // 传递变量    public ChangeData(long delta, long loopCount, DataHolder dataHolder) {        this.delta = delta;        this.loopCount = loopCount;        this.dataHolder = dataHolder;    }    // 重载run方法    @Override    public void run() {        for (int i = 0; i < loopCount; i++) {            dataHolder.change(delta);        }        dataHolder.print();    }}
  • 使用单线程来跑,结果没有溢出
代码语言:javascript
复制
/** * @author: 毛利 */public class SingleThreadSimple {    public static void main(String[] args) {        // TODO 对一个数据进行相同次数的加减,而且也没有溢出,最后的结果应该是0        DataHolder dataHolder = new DataHolder();        // 4294967294 Integer.MAX_VALUE        ChangeData increase = new ChangeData(2, Integer.MAX_VALUE, dataHolder);        increase.run();        ChangeData decrease = new ChangeData(-2, Integer.MAX_VALUE, dataHolder);        decrease.run();        // 0    }}

但是对于多线程,结果就不一样了

代码语言:javascript
复制
public class MultiThreadChaos {    public static void main(String[] args) {        // TODO 同样的运算,安排在两个线程里做,结果就不一样了        DataHolder dataHolder = new DataHolder();        // TODO 创建两个线程 increaseThread 和 decreaseThread        Thread increaseThread = new Thread(new ChangeData(2, Integer.MAX_VALUE, dataHolder));        Thread decreaseThread = new Thread(new ChangeData(-2, Integer.MAX_VALUE, dataHolder));        System.out.println("执行开始");        increaseThread.start();        decreaseThread.start();        /*        执行开始        Number=-4219868950        Number=-4294408024         */    }}

解决方法:一个 synchronized 解决问题

代码语言:javascript
复制
  public synchronized static void changeStatic(long delta) {        numberStatic += delta;    }

除了 synchronized,还有另一个 volatile 关键字,不仅仅在 Java 语言中有,在很多语言中都有的,而且其用法和语义也都是不尽相同的。

volatile 通常被比喻成 "轻量级的 synchronized",也是 Java 并发编程中比较重要的一个关键字。

和 synchronized 不同,volatile 是一个变量修饰符,只能用来修饰变量。无法修饰方法及代码块等。

相比 Sychronized(重量级锁,对系统性能影响较大),volatile 提供了另一种解决 原子性,可见性和有序性

补充几个概念

  • 共享变量:共享变量是指可以同时被多个线程访问的变量,共享变量是被存放在堆里面,所有的方法内临时变量都不是共享变量。(共享变量是被存放在堆内存中的)
  • 可见性:内存的可见性是指线程之间的可见性,一个线程的修改状态对另外一个线程是可见的,用通俗的话说,就是假如一个线程 A 修改一个共享变量 flag 之后,则线程 B 去读取,一定能读取到最新修改的 flag。
  • 原子性:Java 中,对基本数据类型的读取和赋值操作是原子性操作,所谓原子性操作就是指这些操作是不可中断的,要做一定做完,要么就没有执行。比如:
  • 有序性:Java 内存模型中,允许编辑器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

volatile 都是在 cpu 指令级别进行操作。volatile 是无法保证变量安全的:

volatile 变量只能确保可见性。

Java 内存模型(JMM)

java 内存模型 (Java Memory Model,JMM) 是 java 虚拟机规范定义的,用来屏蔽掉 java 程序在各种不同的硬件和操作系统对内存的访问的差异,这样就可以实现 java 程序在各种不同的平台上都能达到内存访问的一致性。

大家都知道计算机都是多核的,在多核中,不仅面临如上问题,还有如果多个核用到了同一个数据,如何保证数据的一致性、正确性等问题,也是必须要解决的。所以协调 cpu 和各个硬件之间的速度差异是非常重要的,要不然 cpu 就一直在等待,浪费资源。

JMM 涉及到的概念有:

  • 主内存:java 虚拟机规定所有的变量 (不是程序中的变量) 都必须在主内存中产生,为了方便理解,可以认为是堆区。可以与前面说的物理机的主内存相比,只不过物理机的主内存是整个机器的内存,而虚拟机的主内存是虚拟机内存中的一部分。
  • 工作内存:java 虚拟机中每个线程都有自己的工作内存,该内存是线程私有的为了方便理解,可以认为是虚拟机栈。可以与前面说的高速缓存相比。线程的工作内存保存了线程需要的变量在主内存中的副本。虚拟机规定,线程对主内存变量的修改必须在线程的工作内存中进行,不能直接读写主内存中的变量。
指令重排 demo

来源: 极客时间

代码语言:javascript
复制
public class DataHolder {    int a, b, c, d, f, g;    long e;    // TODO 有 volatile 修饰就会影响之前的指令重排    // volatile long e;    public void operateData() {        // TODO 按照这个顺序执行,g 的值是肯定小于等于 e 的。但是实际执行在执行的时候,可能会为了优化的目的重排        a += 1;        b += 1;        c += 1;        d += 1;        e += 1;        f += 1;        g += 1;    }    int counter;    // 计算触发重排多少次    public void check() {        // TODO 看似不可能的条件,实际可能被触发到        if (g > e) {            System.out.println("got it " + (counter++));        }    }}
代码语言:javascript
复制
public class VolatileAppMain {    public static void main(String[] args) {        DataHolder dataHolder = new DataHolder();        Thread operator = new Thread(() -> {            while (true) {                dataHolder.operateData();            }        });        operator.start();        Thread checker = new Thread(() -> {            while (true) {                dataHolder.check();            }        });        checker.start();    }}

从结果来看一直存在指令重排

代码语言:javascript
复制
public class VolatileAppMain {    public static void main(String[] args) {        DataHolder dataHolder = new DataHolder();        Thread operator = new Thread(() -> {            while (true) {                dataHolder.operateData();            }        });        operator.start();        Thread checker = new Thread(() -> {            while (true) {                dataHolder.check();            }        });        checker.start();    }}

加上 volatile关键字

代码语言:javascript
复制
volatile long e;

就不存在所谓的指令重排

volatile 的可见性

volatile 变量只能确保可见性。

代码语言:javascript
复制
/** * @author: 毛利 */public class Test {//    public static final int num = 1;    private /*volatile*/ boolean flag = true;//    private volatile boolean flag = true;    public void testvolatile(){        System.out.println("开始读取……");        while (flag){//            System.out.println(num);        }        System.out.println("读取完成……");    }    public static void main(String[] args) {        Test test = new Test();        new Thread(test::testvolatile).start();        try {            Thread.sleep(1000);        } catch (InterruptedException e) {            e.printStackTrace();        }        test.flag = false;    }}

一直卡死在哪里

如果加上了 volatile,那么 flag 就是可见了

如果可以读取静态变量 num,flag 也可以可见了

果然读取完内存,然后读取完成

参考: https://www.jianshu.com/p/15106e9c4bf3 https://www.cnblogs.com/laowen-zjw/p/6735790.html

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-11-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小刘IT教程 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 多线程混乱
    • 回顾 synchronized 关键字
    • Java 内存模型(JMM)
      • 指令重排 demo
      • volatile 的可见性
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档