Java 多线程 --- 多线程的相关概念

news/2024/3/29 23:36:43/文章来源:https://blog.csdn.net/weixin_38803409/article/details/129215783

Java 多线程 --- 多线程的相关概念

  • Race Condition 问题
  • 并发编程的性质 --- 原子性, 可见性, 有序性
  • 上下文切换 (Context Switch)
  • 线程的一些故障 --- 死锁, 活锁, 饥饿
    • 死锁 (Deadlock)
    • 活锁(Livelock)
    • 死锁和活锁的区别
    • 饥饿(Starvation)

背景: 操作系统 — 线程/进程 同步

Race Condition 问题

  • 下面的代码会创建100个银行账户,每一个账户初始金额是1000. 然后不段的随机给另外一个账户转钱.
  • 不管怎么转钱, 100个账户的总金额应该一直保持为10000.
  • 但是有的输出结果总金额不到10000, 因为出现了对critical section资源竞争的问题
package SimpleThread;
import java.util.*;public class Bank {private final double[] accounts;public Bank(int n, double initialBalance) {accounts = new double[n];Arrays.fill(accounts, initialBalance);}public void transfer(int from, int to, double amount) {if (accounts[from] < amount) return;System.out.print(Thread.currentThread());accounts[from] -= amount;System.out.printf(" %10.2f from %d to %d", amount, from, to);accounts[to] += amount;System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());}public double getTotalBalance() {double sum = 0;for (double a : accounts)sum += a;return sum;}public int size() {return accounts.length;}}
package SimpleThread;
public class UnsynchBankTest {public static final int NACCOUNTS = 100;public static final double INITIAL_BALANCE = 1000;public static final double MAX_AMOUNT = 1000;public static final int DELAY = 10;public static void main(String[] args){Bank bank = new Bank(NACCOUNTS, INITIAL_BALANCE);for (int i = 0; i < NACCOUNTS; i++) {int fromAccount = i;Runnable r = () -> {try{while (true) {int toAccount = (int) (bank.size() * Math.random());double amount = MAX_AMOUNT * Math.random();bank.transfer(fromAccount, toAccount, amount);Thread.sleep((int) (DELAY * Math.random()));}}catch (InterruptedException e) {}};Thread t = new Thread(r);t.start();}}
}

在这里插入图片描述

  • 因为 accounts[to] += amount;accounts[from] -= amount;不是原子操作, 也就是此行代码分为三条指令
  • step1: 将accounts[i] 放入寄存器
  • step2: 将amount增加或者减少
  • step3: 将结果放回accounts[i]
  • 假设线程1执行完前两步之后被打断, 线程2执行全部三个步骤, 然后线程1继续执行第三步, 则线程2的数据会被线程1的第三步覆盖

在这里插入图片描述

并发编程的性质 — 原子性, 可见性, 有序性

原子性

  • 一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。比如上面的例子就没有保证原子性
  • 注意: 原子操作 + 原子操作 != 原子操作
  • 如下: 如果a是原子操作, b也是原子操作 但是 a + b 不是原子操作
a = 1
b = 2
  • 在 Java 语言中, 除去 long 类型和double 类型, 剩下所有类型 (包括基础类型和引用类型) 的 写(write) 操作都是原子操作.
  • 写入是原子操作: byte, boolean, short, char, float, int 和其他引用操作
  • 写入不是原子操作: long, double
  • 在 Java 语言中, 所有类型的读取操作都是原子操作

可见性
在这里插入图片描述

  • 由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间)
  • 工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存
  • 主内存是共享内存区域,所有线程都可以访问.
  • 线程对变量的操作(读取赋值等)必须在工作内存中进行
  • 首先要将变量从主内存拷贝到自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写会主内存
  • 不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成
  • 当一个线程修改了共享变量的值时,其他线程能够立即得知这个修改, 这个就是可见性

有序性

  • 当发生指令重排时, 线程就失去了有序性
  • 在源代码顺序与程序执行顺序不一致的情况下, 就发生了指令重排序
  • 编译器出于性能的考虑, 在其认为不影响程序正确性的情况下可能会对源代码顺序进行调整, 从而造成程序顺序与相应的源代码顺序不一致.

上下文切换 (Context Switch)

  • 当线程切入和切出操作系统的时候需要保存和恢复相应的线程的进度信息(如程序运行到什么程度了,计算的中间结果以及执行到了哪条指令)
  • 这个进度的信息被称为上下文 (Context). 切换的过程叫做上下文切换(Context Switch)
  • 一个线程的生命周期状态在 Runnable 状态 与 非Runnable状态之 (包括Blocked, Waiting) 之间切换的过程就是一个上下文切换的过程

上下文切换的原因 包括主动切换和被动切换

  • 主动切换的原因包括以下
  • Thread.sleep
  • Object.wait()
  • Thread.yield()
  • Thread.join()
  • 被动切换的原因包括
  • 线程的时间片用完
  • 优先级低的线程被优先级高的线程切换
  • Java虚拟机也会导致被动上下文切换, 因为垃圾回收器可能需要暂停所有线程才能完成垃圾回收

线程的一些故障 — 死锁, 活锁, 饥饿

死锁 (Deadlock)

  • 死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程
  • 死锁的发生条件:
  • 当前进程或线程拥有其他进程或线程需要的资源
  • 当前进程或线程等待其他进程或线程已拥有的资源
  • 都不放弃自己拥有的资源,也就是不能被其他进程或线程剥夺,只能在使用完以后由自己释放

Example 1:

mutex m
function {lock(m) //成功拿到锁lock(m) //拿不到锁,因为已经被自己拿了,所以会无限等待下去//critical sectionunlock(m)unlock(m)
}

Example 2:
在这里插入图片描述

  • task A成功拿到M1的锁,同时task B成功拿到M2的锁
  • task A等待获取M2的锁,同时task B等待获取M1的锁
  • task A只有获得M2的锁才能往下继续然后释放M1的锁
  • task B只有获得M1的锁才能往下继续然后释放M2的锁

活锁(Livelock)

  • 活锁是不同线程占用对方所需要的资源, 导致任何线程都无法继续向前运行, 但是没有一个线程处于block状态. 而每个线程一遍又一遍的不断尝试获取资源并不断消耗CPU资源.

Example 1:

  • 一个系统可以运行的进程总量由process table有多少个entry决定.
  • 如果一个process table满了会导致一个进程fork子进程失败. 而失败以后父进程不会进入阻塞状态, 而是等待一段时间后再次fork子进程.
  • 假设一个系统有100个entry. 10个父进程需要各创建12个子进程, 当新创建90个进程之后, process table被占满. 而10个父进程会不断的重新fork子进程.

Example 2:

public class CommonResource {private Worker owner;public CommonResource (Worker d) {owner = d;}public Worker getOwner () {return owner;}public synchronized void setOwner (Worker d) {owner = d;}
}
public class Worker {private String name;private boolean active;public Worker (String name, boolean active) {this.name = name;this.active = active;}public String getName () {return name;}public boolean isActive () {return active;}public synchronized void work (CommonResource commonResource, Worker otherWorker) {while (active) {// wait for the resource to become available.if (commonResource.getOwner() != this) {try {wait(10);} catch (InterruptedException e) {//ignore}continue;}// If other worker is also active let it do it's work firstif (otherWorker.isActive()) {System.out.println(getName() +" : handover the resource to the worker " +otherWorker.getName());commonResource.setOwner(otherWorker);continue;}//now use the commonResourceSystem.out.println(getName() + ": working on the common resource");active = false;commonResource.setOwner(otherWorker);}}
}
public class Livelock {public static void main (String[] args) {final Worker worker1 = new Worker("Worker 1 ", true);final Worker worker2 = new Worker("Worker 2", true);final CommonResource s = new CommonResource(worker1);new Thread(() -> {worker1.work(s, worker2);}).start();new Thread(() -> {worker2.work(s, worker1);}).start();}
}
  • 以上代码当两个线程都是active时, 会互相谦让对方, 然后不断重新尝试, 进入livelock

output:

Worker 1  : handing over the resource to the worker: Worker 2
Worker 2 : handing over the resource to the worker: Worker 1
Worker 1  : handing over the resource to the worker: Worker 2
Worker 2 : handing over the resource to the worker: Worker 1
Worker 1  : handing over the resource to the worker: Worker 2
Worker 2 : handing over the resource to the worker: Worker 1

死锁和活锁的区别

  • 活锁和死锁非常的像, 都是线程互相持有自己所需要的资源导致程序不能正常往下运行
  • 区别是死锁的线程会进入等待状态, 也就是block状态
  • 而活锁的进程不会进入等待状态, 而是会不断的尝试运行, 不断的消耗CPU资源.

饥饿(Starvation)

  • 是指一个可运行的进程尽管能继续执行,但被调度器无限期地忽视,而不能被调度执行的情况

Example:

  • 如果进程按照优先级运行, 也就是先运行高优先级的进程
  • 那么如果一直有新的高优先级的进程被创建, 则低优先级的进程永远不会被执行, 进入饥饿状态

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_75040.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【unity学习记录】Canvas Group组件

&#x1f497; 未来的游戏开发程序媛&#xff0c;现在的努力学习菜鸡 &#x1f4a6;本专栏是我关于游戏开发的学习笔记 &#x1f236;本篇是unity的Canvas Group组件 Canvas Group画布组介绍详解1. Alpha2. Interactable3. Blocks Raycasts4. Ignore Parent Groups介绍 画布组…

6.0.4:GrapeCity Documents for Excel GcExcel Crack

在更短的时间内生成 Excel 电子表格&#xff0c;不依赖于 Excel&#xff01; 在任何应用程序中转换、计算、格式化和解析电子表格。 快速高效&#xff1a;其轻巧的尺寸意味着 Documents for Excel 针对快速处理大型 Excel 文档进行了优化 使用适用于 Windows、Linux 和 Mac 的…

JVM 学习(2)—简单理解强、软、弱、虚 Java 四大引用

一、Java 引用概述 Java 中出现四种引用是为了更加灵活地管理对象的生命周期&#xff0c;以便在不同场景下灵活地处理对象的回收问题。不同类型的引用在垃圾回收时的处理方式不同&#xff0c;可以用来实现不同的垃圾回收策略。Java 目前将其分成四类&#xff0c;类图如下&…

mysql一两种索引方式hash和btree

1. Hash索引&#xff1a; Hash 索引结构的特殊性&#xff0c;其检索效率非常高&#xff0c;索引的检索可以一次定位&#xff0c;不像B-Tree 索引需要从根节点到枝节点&#xff0c;最后才能访问到页节点这样多次的IO访问&#xff0c;所以 Hash 索引的查询效率要远高于 B-Tree 索…

信息安全概论之《密码编码学与网络安全----原理与实践(第八版)》

前言&#xff1a;在信息安全概论课程的学习中&#xff0c;参考了《密码编码学与网络安全----原理与实践&#xff08;第八版&#xff09;》一书。以下内容为以课件为主要参考&#xff0c;课本内容与网络资源为辅助参考&#xff0c;学习该课程后作出的总结。 一、信息安全概述 1…

力扣Top100题之两数相加(Java解法)

0 题目描述 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外&#xff0c;这两个数…

MVCC 当前读 快照读 RC read view RR下事务更新不会丢失

MVCC(multi-version-concurrent-control) MVCC是行锁的一个变种&#xff0c;但MVCC在很多情况下它避免了加锁。不是buffer块&#xff0c;而是buffer中的记录行。 MVCC (Multi-Version Concurrency Control) (注&#xff1a;与MVCC相对的&#xff0c;是基于锁的并发控制&#x…

【无限思维画布】制作思维导图第五步,节点创建与连接,拖拽对齐与双击缩放

正在为无限词典制作单词思维导图功能&#xff0c;实现无限单词导图&#xff0c;无限思维画布。目前制作到第五步&#xff0c;实现节点创建、节点连接、节点拖拽对齐&#xff1a; 节点创建与连接&#xff0c;拖拽对齐Details 第四步&#xff0c;推倒重来。 一开始受vue-mindma…

【Python小程序】这款成语接龙小程序,让小孩儿轻松记住500个成语,在家里常玩,语文直上135,仅此一份,快收藏~(太强大了)

前言 学习路上&#xff0c;那我同行。 哈喽~大家晚上好呀&#xff0c;我是你们的栗子同学。 所有文章完整的素材源码都在&#x1f447;&#x1f447; 粉丝白嫖源码福利&#xff0c;请移步至CSDN社区或文末公众hao即可免费。 成语接龙是一个传统的文字游戏。很多孩子都喜欢玩。…

Lesson9---回归问题

9.1 机器学习基础 课程回顾 Python语言基础Numpy/Matplotlib/Pandas/Pillow应用TensorFlow2.0 低阶API 即将学习 机器学习、人工神经网络、深度学习、卷积神经网络典型模型的TensorFlow2.0实现 9.1.1 机器学习 机器学习&#xff08;Machine Learning&#xff09;&#xf…

关于C语言中最大公因数的思考

目录 如何去求最大公因数利用枚举法&#xff1a; 如何去求最大公因数利用辗转相除法&#xff1a; 例1&#xff1a;最大公因数使用for循环和if语句 示例2&#xff1a;最大公因数使用while循环和if ... else语句 例3&#xff1a;正负数均为最大公因数 如何去求最大公因数利用…

【2023-02-27】349.两个数组的交集

题目链接&#xff1a;349.两个数组的交集 解题思路&#xff1a;点我 class Solution { public:vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {unordered_set<int>res_set;unordered_set<int>nums1_set(nums1.beg…

qt-c++进阶1-window、linux下获取本机所有网卡ip信息、根据网卡名获取ip地址。

系列文章目录 例如&#xff1a;第一章 主要是通过qt-c实现获取本机电脑的网卡信息或者是IP信息 文章目录系列文章目录前言一、获取本机网卡IP信息1.1 获取ip地址方法1.2 代码实例总结前言 总结c获取本机网卡信息的方法 第一章&#xff1a;适用于windows操作系统、linux操作系…

Aspose.Slides for .NET 授权须知

Aspose.Slides是一款用于生成&#xff0c;管理和转换PowerPoint幻灯片的本机API&#xff0c;可以使用多种格式&#xff0c;而不需要Microsoft PowerPoint。并且可在任何平台上操作PowerPoint演示文稿。 Aspose.Slides for .NET是一款.NET PowerPoint管理API&#xff0c;用于读…

常用逻辑运算符

逻辑符号表格 逻辑符号含义描述&按位与将数字转成二进制计算&#xff0c;两个位都为1时&#xff0c;结果才为1|或两个位都为0时&#xff0c;结果才为0 &#xff0c;反知任何一个为1结果为1^异或两个位相同为0&#xff0c;不同为1<<左移整体二进位全部左移若干位&…

webrtc音视频通话(一)搭建turn服务器

全球定位&#xff1a;webrtc音视频通话&#xff08;一&#xff09;搭建turn服务器webrtc音视频通话&#xff08;二&#xff09;简单音视频通话webrtc音视频通话&#xff08;三&#xff09;整合websocket在学习webrtc之前呢&#xff0c;需要对websocket有一定基础&#xff0c;如…

腾讯云卖向“有币”区块链

曾经坚决“不涉币”的腾讯云将业务延伸向“有币区块链”。 在首届 Web3 全球峰会“腾讯云Web3构建日”上&#xff0c;腾讯云宣布进军Web3&#xff0c;并公开了与Ankr、Avalanche、Scroll和Sui 四个原生区块链项目的合作&#xff0c;其中前两个项目都发行了加密货币&#xff0c…

比特数据结构与算法(第四章_中_续②)堆解决Topk问题(最小的k个数)

TopK问题介绍&#xff1a;在N个数中找出最大/小的前K个 &#xff08;比如在1000个数中找出最大/小的前10个&#xff09;以前的方法&#xff1a;冒泡排序。时间复杂度&#xff1a; O(N^2)现在找最大的k个数的方法&#xff1a;方法1&#xff1a;堆排序降序&#xff0c;前N个就是最…

使用非对称加密(RSA) 实现前端加密后端解密

数据加密方式有&#xff1a; 单向加密、对称加密、非对称加密、加密盐、散列函数、数字签名。 1、单向加密 单向加密通过对数据进行摘要计算生成密文&#xff0c;密文不可逆推还原。只能加密&#xff0c;不能解密&#xff0c;常用于提取数据的指纹信息以此来验证数据的完整性…

JVM内存溢出与内存泄露

1. 什么是内存溢出? 当前创建的对象的大小大于可用的内存容量大小&#xff0c;发生内存溢出。2. 什么是内存泄露? 该回收的垃圾对象没有被回收&#xff0c;发生了内存泄露&#xff0c;垃圾对象越堆越多&#xff0c; 可用内存越来越少&#xff0c;若可用内存无法存放新的垃圾…