1、小故事

  • 老王(操作系统)有一个功能强大的算盘(CPU),现在想把它租出去,赚一点外快

  • 小南、小女(不同的线程)来使用这个算盘来进行一些计算,并按照时间给老王支付费用

  • 但小南不能一天24小时使用算盘,他经常要小憩一会(sleep),又或是去吃饭上厕所(阻塞 io 操作),有时还需要一根烟,没烟时思路全无(wait)这些情况统称为(阻塞)

  • 在这些时候,算盘没利用起来(不能收钱了),老王觉得有点不划算

  • 另外,小女也想用用算盘,如果总是小南占着算盘,让小女觉得不公平

  • 于是,老王灵机一动,想了个办法 [ 让他们每人用一会,轮流使用算盘 ]

  • 这样,当小南阻塞的时候,算盘可以分给小女使用,不会浪费,反之亦然

  • 最近执行的计算比较复杂,需要存储一些中间结果,而学生们的脑容量(工作内存)不够,所以老王申请了一个笔记本(主存),把一些中间结果先记在本上

  • 计算流程是这样的

  • 但是由于分时系统,有一天还是发生了事故

  • 小南刚读取了初始值 0 做了个 +1 运算,还没来得及写回结果

  • 老王说 [ 小南,你的时间到了,该别人了,记住结果走吧 ],于是小南念叨着 [ 结果是1,结果是1...] 不甘心地到一边待着去了(上下文切换)

  • 老王说 [ 小女,该你了 ],小女看到了笔记本上还写着 0 做了一个 -1 运算,将结果 -1 写入笔记本

  • 这时小女的时间也用完了,老王又叫醒了小南:[小南,把你上次的题目算完吧],小南将他脑海中的结果 1 写入了笔记本

  • 小南和小女都觉得自己没做错,但笔记本里的结果是 1 而不是 0

2、Java 的体现

两个线程对初始值为 0 的静态变量一个做自增,一个做自减,各做 5000 次,结果是 0 吗?

static int counter = 0;

public static void main(String[] args) throws InterruptedException {
   Thread t1 = new Thread(() -> {
       for (int i = 0; i < 5000; i++) {
           counter++;
      }
  }, "t1");

   Thread t2 = new Thread(() -> {
       for (int i = 0; i < 5000; i++) {
           counter--;
      }
  }, "t2");

   t1.start();
   t2.start();
   t1.join();
   t2.join();
   log.debug("{}",counter);
}

多做几次实验,可以得出答案明显不全是0。

3、问题分析

以上的结果可能是正数、负数、零。为什么呢?因为 Java 中对静态变量的自增,自减并不是原子操作,要彻底理解,必须从字节码来进行分析

例如对于 i++ 而言(i 为静态变量),实际会产生如下的 JVM 字节码指令:

getstatic     i  // 获取静态变量i的值
iconst_1         // 准备常量1
iadd             // 自增
putstatic     i  // 将修改后的值存入静态变量i

而对应 i-- 也是类似:

getstatic     i  // 获取静态变量i的值
iconst_1         // 准备常量1
isub             // 自减
putstatic     i  // 将修改后的值存入静态变量i

而 Java 的内存模型如下,完成静态变量的自增,自减需要在主存和工作内存中进行数据交换:

如果是单线程以上 8 行代码是顺序执行(不会交错)没有问题:

但多线程下这 8 行代码可能交错运行:

出现负数的情况:

出现正数的情况:
 
 

4、临界区 Critical Section

  • 一个程序运行多个线程本身是没有问题的

  • 问题出在多个线程访问共享资源

    • 多个线程读共享资源其实也没有问题

    • 在多个线程对共享资源读写操作时发生指令交错,就会出现问题

  • 一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区

例如,下面代码中的临界区

static int counter = 0;

static void increment()
// 临界区
{    
   counter++;
}

static void decrement()
// 临界区
{    
   counter--;
}

5、竞态条件 Race Condition

多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件

Java并发(十三)----共享存在的问题的更多相关文章

  1. Java并发-对象共享

    我们不仅希望防止某个线程正在使用对象状态而其他的线程正在修改该状态,而且希望当一个线程修改了对象状态后,其他的线程能够看到发生的状态变化. 可见性:当读操作和写操作在不同的线程中进行时,他们的动作是共 ...

  2. 《Java并发编程的艺术》笔记

    第1章 并发编程的挑战 1.1 上下文切换 CPU通过时间片分配算法来循环执行任务,任务从保存到再加载的过程就是一次上下文切换. 减少上下文切换的方法有4种:无锁并发编程.CAS算法.使用最少线程.使 ...

  3. 《Java并发编程实战》学习笔记 线程安全、共享对象和组合对象

    Java Concurrency in Practice,一本完美的Java并发参考手册. 查看豆瓣读书 推荐:InfoQ迷你书<Java并发编程的艺术> 第一章 介绍 线程的优势:充分利 ...

  4. 《Java并发编程实战》第三章 对象的共享 读书笔记

    一.可见性 什么是可见性? Java线程安全须要防止某个线程正在使用对象状态而还有一个线程在同一时候改动该状态,并且须要确保当一个线程改动了对象的状态后,其它线程能够看到发生的状态变化. 后者就是可见 ...

  5. Java并发系列[3]----AbstractQueuedSynchronizer源码分析之共享模式

    通过上一篇的分析,我们知道了独占模式获取锁有三种方式,分别是不响应线程中断获取,响应线程中断获取,设置超时时间获取.在共享模式下获取锁的方式也是这三种,而且基本上都是大同小异,我们搞清楚了一种就能很快 ...

  6. 【Java并发编程一】线程安全和共享对象

    一.什么是线程安全 当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步及在调用代码代码不必作其他的协调,这个类的行为仍然是正确的,那么称这个类是线程安全的 ...

  7. 《java并发编程实战》读书笔记2--对象的共享,可见性,安全发布,线程封闭,不变性

    这章的主要内容是:如何共享和发布对象,从而使它们能够安全地由多个线程同时访问. 内存的可见性 确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化. 上面的程序中NoVisibility可能 ...

  8. java并发程序和共享对象实用策略

    java并发程序和共享对象实用策略 在并发程序中使用和共享对象时,可以使用一些实用的策略,包括: 线程封闭 只读共享.共享的只读对象可以由多个线程并发访问,但任何线程都不能修改它.共享的只读对象包括不 ...

  9. Java并发指南9:AQS共享模式与并发工具类的实现

    一行一行源码分析清楚 AbstractQueuedSynchronizer (三) 转自:https://javadoop.com/post/AbstractQueuedSynchronizer-3 ...

  10. Java并发编程原理与实战三十三:同步容器与并发容器

    1.什么叫容器? ----->数组,对象,集合等等都是容器.   2.什么叫同步容器? ----->Vector,ArrayList,HashMap等等.   3.在多线程环境下,为什么不 ...

随机推荐

  1. Linux运维实战项⽬进阶

    项⽬描述 项⽬需求 近年来为适应业务发展的需求,世界500强XX企业准备进⾏⼤规模的电⼦商务建设, 同时,希望能通过Linux平台,利⽤开源技术,来实现⼤型互联⽹电⼦商务⽹站架构建设和业务⽀撑,现要求 ...

  2. Git&GitHub简介与入手(一)

    一.Git版本控制 1.集中式版本控制工具:SVN(版本控制集中在服务器端,会有单点故障风险): 2.分布式版本控制工具:Git: 3.Git简史 Talk is cheap, show me the ...

  3. 2022-11-03:给定一个数组arr,和一个正数k 如果arr[i] == 0,表示i这里既可以是左括号也可以是右括号, 而且可以涂上1~k每一种颜色 如果arr[i] != 0,表示i这里已经确

    2022-11-03:给定一个数组arr,和一个正数k 如果arr[i] == 0,表示i这里既可以是左括号也可以是右括号, 而且可以涂上1~k每一种颜色 如果arr[i] != 0,表示i这里已经确 ...

  4. 2021-04-30:一条直线上有居民点,邮局只能建在居民点上。给定一个有序正数数组arr,每个值表示 居民点的一维坐标,再给定一个正数 num,表示邮局数量。选择num个居民点建立num个 邮局,使

    2021-04-30:一条直线上有居民点,邮局只能建在居民点上.给定一个有序正数数组arr,每个值表示 居民点的一维坐标,再给定一个正数 num,表示邮局数量.选择num个居民点建立num个 邮局,使 ...

  5. mysql 新建数据库 排序规则

    utf8_unicode_ci和utf8_general_ci对中.英文来说没有实质的差别.utf8_general_ci校对速度快,但准确度稍差.utf8_unicode_ci准确度高,但校对速度稍 ...

  6. MVC 三层架构案例详细讲解

    MVC 三层架构案例详细讲解 @ 目录 MVC 三层架构案例详细讲解 每博一文案 1. MVC 概述 2. MVC设计思想 3. 三层架构 4. MVC 与 三层架构的关系: 5. 案例举例:用户账户 ...

  7. web自动化08-下拉选择框、弹出框、滚动条

    1.下拉选择框操作 下拉框就是HTML中<select>元素:   先列需求: 需求:使用'注册A.html'页面,完成对城市的下拉框的操作 1).选择'广州' 2).暂停2秒,选择'上海 ...

  8. 聊聊CSS 缓动函数的新成员linear()

    CSS 缓动函数是一种用于控制 CSS 动画过渡效果的函数,可以让动画变得更加自然.这篇文章将介绍一种新的 CSS easing function,即 linear(),它可以模拟出更复杂的缓动效果, ...

  9. Java发展史、JDK环境配置、运行原理及开发工具(学习的前期准备)

    一.Java的历史和三大版本 1.Java的发展史 Java由sun公司开发,Java之父James Gosling,Java是一门面向对象的编程语言,也称为"高级编程语言" Ja ...

  10. 在 Linux 和 Windows 下源码安装 Perl

    Perl 是一种功能丰富的计算机程序语言,运行在超过 100 种计算机平台上,适用广泛,从大型机到便携设备,从快速原型创建到大规模可扩展开发.在生物信息分析领域,Perl 主要是做数据预处理.文本处理 ...