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

news2024/11/18 7:34:43

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 section
	unlock(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 first
            if (otherWorker.isActive()) {
                System.out.println(getName() +
                            " : handover the resource to the worker " +
                                                       otherWorker.getName());
                commonResource.setOwner(otherWorker);
                continue;
            }

            //now use the commonResource
            System.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.coloradmin.cn/o/374997.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows操作系统的体系结构、运行环境和运行状态

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天我们来重新审视一下Windows这个我们熟悉的不能再熟悉的系统。说Windows操作系统的运行环境和运行状态&#xff0c;首先要介绍一下Windows操作系统的体系结构&#xff0c;然后再要说到最重要的两个概念:核…

【架构师】零基础到精通——微服务体系

博客昵称&#xff1a;架构师Cool 最喜欢的座右铭&#xff1a;一以贯之的努力&#xff0c;不得懈怠的人生。 作者简介&#xff1a;一名Coder&#xff0c;软件设计师/鸿蒙高级工程师认证&#xff0c;在备战高级架构师/系统分析师&#xff0c;欢迎关注小弟&#xff01; 博主小留言…

Iterator和Genertator

一、Iterator迭代器和for of原理 * 遍历器&#xff08;Iterator&#xff09;是一种机制(接口)&#xff1a;为各种不同的数据结构提供统一的访问机制&#xff0c;任何数据结构只要部署Iterator接口&#xff0c;就可以完成遍历操作「for of循环」&#xff0c;依次处理该数据结构的…

【C++入门(下篇)】C++引用,内联函数,auto关键字的学习

前言&#xff1a; 在上一期我们进行了C的初步认识&#xff0c;了解了一下基本的概念还学习了包括&#xff1a;命名空间&#xff0c;输入输出以及缺省参数等相关的知识。今天我们将进一步对C入门知识进行学习&#xff0c;主要还需要大家掌握我们接下来要学习的——引用&#xf…

基于SpringCloud的可靠消息最终一致性06:轮询事务消息

上一节把可靠消息最终一致性的正常逻辑代码顺序执行了一次,并且对于同一个事务消息,在正常情况下它要被发送至少两次。 这是因为在发送消息之前,TransactionMessageService就已经把消息保存到了数据库中。而在首次消费完消息后,TransactionMessageListener并没有从数据库中…

冯诺依曼体系结构与操作系统的概念及理解

一、 冯诺依曼体系结构1、概念2、内存的作用3、硬件原理解释软件行为二、操作系统的概念及基本作用1、概念2、设计操作系统的目的3、操作系统的主要作用4、什么是管理5、管理的目的6、操作系统如何为我们服务一、 冯诺依曼体系结构 我们常见的计算机&#xff0c;如笔记本。我们…

只需四步,手把手教你打造专属数字人

伴随ChatGPT的问世&#xff0c;在技术与商业运作上都日渐发展成熟的数字人产业正持续升温。去年9月&#xff0c;北京市发布了国内首个数字人产业专项支持政策&#xff0c;提出将依托国家文化专网将数字人纳入文化数据服务平台。以数字人、ChatGPT为代表的互联网3.0创新应用产业…

【2023】OAK智能深度相机用户实际应用项目(附开源代码)

编辑&#xff1a;OAK中国 首发&#xff1a;oakchina.cn 喜欢的话&#xff0c;请多多&#x1f44d;⭐️✍ 内容可能会不定期更新&#xff0c;官网内容都是最新的&#xff0c;请查看首发地址链接。 ▌前言 Hello&#xff0c;大家好&#xff0c;这里是OAK中国&#xff0c;我是助手…

Linux环境内存管理——分配内存和释放内存

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天我们来重新审视一下Windows程序员如何学习Linux环境内存管理。由于很多程序在Windows环境下开发好后&#xff0c;还要部署到Linux服务器上去&#xff0c;所以作为Windows程序员有必要学习Linux环境的内存…

IntelliJ IDEA 实用插件推荐(包含使用教程)

IntelliJ IDEA 实用插件推荐 背景&#xff1a;电脑重装了&#xff0c;重新下载了最新版的IntelliJ IDEA&#xff0c;感觉默认模式有点枯燥&#xff0c;于是决定从网上下载一些实用美观的插件优化自己以后吃饭的工具&#xff0c;现在推荐的都是目前还能用的&#xff08;亲身实践…

【java】Java 内存模型

文章目录前言什么是 Java 内存模型为什么需要 Java 内存模型顺序一致性内存模型Happens-Before 规则总结前言 在并发编程中&#xff0c;当多个线程同时访问同一个共享的可变变量时&#xff0c;会产生不确定的结果&#xff0c;所以要编写线程安全的代码&#xff0c;其本质上是对…

C语言青蛙跳台阶【图文详解】

青蛙跳台阶前言1. 题目介绍2. 解题思路3. 利用图片来演示青蛙跳台阶的原理4. 如何用C语言实现青蛙跳台阶前言 在本文&#xff0c;我们要与一只活泼可爱的小青蛙合作&#xff0c;带领着它跳上台阶&#xff0c;这个小家伙精力充沛&#xff0c;特别擅长于跳跃。我们要让它做我们的…

一个诡异的 Pulsar InterruptedException 异常

背景 今天收到业务团队反馈线上有个应用往 Pulsar 中发送消息失败了&#xff0c;经过日志查看得知是发送消息时候抛出了 java.lang.InterruptedException 异常。 和业务沟通后得知是在一个 gRPC 接口中触发的消息发送&#xff0c;大约持续了半个小时的异常后便恢复正常了&…

MySQL数据库中的函数怎样使用?

函数 是指一段可以直接被另一段程序调用的程序或代码。 也就意味着&#xff0c;这一段程序或代码在MySQL中已经给我们提供了&#xff0c;我们要做的就是在合适的业务场景调用对应的函数完成对应的业务需求即可。 那么&#xff0c;函数到底在哪儿使用呢?我们先来看两个场景&…

前端开发:JS的节流与防抖

前言 在前端实际开发中&#xff0c;有关JS原生的节流和防抖处理也是很重要的点&#xff0c;关于底层和原理的掌握使用&#xff0c;尤其是在性能优化方面甚为重要。作为前端开发的进阶内容&#xff0c;在实际开发过程中节流和防抖通常都是项目优化的必要手段&#xff0c;而且也是…

【Project】项目管理软件学习笔记

一、前言使用Project制定项目计划步骤大致如下&#xff1a;以Project2013为例&#xff0c;按照上图步骤指定项目计划。二、实施2.1 创建空白项目点击文件——新建——空白项目&#xff0c;即完成了空白项目的创建&#xff0c;在此我把该项目保存为60mm项目管理.mpp&#xff0c;…

深入浅出1588v2(PTP)里的时间同步原理

1.时间同步1.1 单步同步(OneStep)单步同步最为简单&#xff0c;master向slave发送一个sync的同步包&#xff0c;同步包里带有这条信息发送时master的当前时间t1&#xff0c;假如这条信息从master传输到slave需要的传输时间是D&#xff0c;那么slave收到信息时&#xff0c;maste…

芯驰(E3-gateway)开发板环境搭建

1-Windows下环境配置 可以在Windows上使用命令行或者IAR IDE编译SSDK项目。Windows编译依赖的工具已经包含在 prebuilts/windows 目录中&#xff0c;包括编译器、Python和命令行工具。 1.1.1 CMD SSDK集成 msys 工具&#xff0c;可以在Windows命令行中完成SDK的配置、编译和…

嵌入式系统硬件设计与实践(第一步下载eda软件)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 现实生活中&#xff0c;我们经常发现有的人定了很多的目标&#xff0c;但是到最后一个都没有实现。这听上去有点奇怪&#xff0c;但确实是实实在在…

Mysql数据库总结

一.MySQL 的基础1.架构图Mysql逻辑架构图主要分三层&#xff1a;&#xff08;1&#xff09;第一层负责连接处理&#xff0c;授权认证&#xff0c;安全等等 &#xff08;2&#xff09;第二层负责编译并优化SQL &#xff08;3&#xff09;第三层是存储引擎。Mysql 服务器的默认端…