(JAVA高并发程序设计)第一章、走进并行世界

简介: (JAVA高并发程序设计)第一章、走进并行世界

一、何去何从的并行

不多哔哔,直接进入正题

1、概念

1.1 同步和异步
同步和异步通常用来形容一次方法的调用,同步方法调用一旦开始,调用者必须等到方法调用返回后,才可以继续后面的行为。异步方法调用更像一个消息传递,一旦开始,方法调用会立即返回,调用者就可以继续后面的操作。而异步方法通常会在另一个线程中进行。整个过程,不会阻碍调用者的工作。
打个比方,比如你找个同学带你上荣耀,你就要和他一起打,直到他带你上了荣耀为止你才会结束,这就是同步。但是你找个代练,你把钱付了剩下就不用管了,代练会自己帮你打。自己可以该干什么干什么,这就是异步。
1.2 并发和并行
并发和并行是两个非常容易被混淆的概念。他们都可以表示多个任务一起执行,但是侧重点不同。并发偏重于多个任务交替执行,意思就是任务一执行一部分再执行任务二再执行任务三,轮流执行,其实是串联。而并行是真正意义上的”同时执行“。对于外部观察者来说,会执行并发也是一起执行的错觉。
1.3 临界区
临界区用来表示一种公共资源或者说共享数据,可以被多个线程使用。但每一次只有一个线程可以使用它,一旦临界区资源被占用,其他线程只能挂起。比如办公室的打印机,一次只能执行一个任务。
在并行程序中,临界区资源是保护的对象,如果出现意外,打印机同时打印两个任务,那么最有可能的结果就是打印出来的文件是坏的。
1.4 阻塞和非阻塞
阻塞和非阻塞通常用来形容多线程间的相互影响。比如一个线程占用了临界区资源,那么其他所有需要这个资源的线程就必须等待。等待会导致线程挂起,这种情况就是阻塞。此时,如果占用资源的线程一直不愿意释放资源,那么其他阻塞的线程都不能工作。
非阻塞的意思与阻塞相反,它强调没有一个线程可以妨碍其他线程执行,所有的线程都会不断向前执行。有关这个概念在后面”并发级别“中会详细介绍
1.5 死锁、饥饿、活锁
死锁、饥饿和活锁都属于多线程的活跃性问题。如果发现上述几种情况,那么相关线程可能就不再活跃,也就是说他可能不再继续执行。
① 死锁是最糟糕的情况,死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去;此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
② 饥饿是指系统不能保证某个进程的等待时间上界,从而使该进程长时间等待,当等待时间给进程推进和响应带来明显影响时,称发生了进程饥饿。当饥饿到一定程度的进程所赋予的任务即使完成也不再具有实际意义时称该进程被饿死。
③ 活锁恰恰与死锁相反,死锁是大家都拿不到资源都占用着对方的资源,而活锁是拿到资源却又相互释放不执行。当多线程中出现了相互谦让,都主动将资源释放给别的线程使用,这样这个资源在多个线程之间跳动”
简单来说,死锁就是我们两个都要做蛋炒饭,你想要我手里的火腿肠,但是我想不给。我想要你手里的鸡蛋,你也不想给,然后我俩的蛋炒饭都执行不下去就一直耗着。饥饿就是我答应给你一个火腿肠让你去炒饭吃,但是没说啥时候给你,就一直让你等,等到最后直接饿死。活锁就是我们都有自己需要的食材但是又互相送,不想炒。

2、并发级别

2.1 阻塞
一个线程是阻塞的,那么其他线程释放资源之前,这个线程无法继续执行。当我们使用synchronized关键字或者重入锁时,我们得到的就是阻塞的线程。
synchronized关键字和重入锁都试图在执行后续代码前,得到临界区的资源,如果得不到,线程就会被挂起等待,直到获得资源为止
2.2 无饥饿
如果线程之间有优先级,那么线程调度的时候总是会先满足优先级高的线程。也就是说,对于同一个资源的分别配是不公平的!公平锁和非公平锁两种情况,对于非公平锁就是说谁优先级高就执行谁,这样有可能会让优先级低的线程饿死。对于公平锁就是谁先来谁先执行按顺序,不畏权贵。
2.3 无障碍
无障碍是一种最弱的非阻塞调度。两个线程如果无障碍的执行,那么不会因为临界区的问题导致一方被挂起。也就是说大家都可以进临界区,那么大家一起修改数据,把数据改坏了怎么办?对于无障碍线程来说,一旦遇到这种情况就进行回滚,回到没修改之前确保数据安全。但如果没有数据竞争发生,那么线程就可以顺利完成自己的工作,走出临界区。

2.4 无锁
无锁的并行都是无障碍的。在无锁的情况下,所有的线程都能尝试对临界区进行访问,但不同的是,无锁的并发保证必然有一个线程能够在有限步内完成操作离开临界区。

2.5 无等待
无等待是在无锁的基础上进行优化,它要求所有的线程都必须在有限的步内完成,这样就不会引起饥饿的问题。

3、有关并行的两个重要定律

有关为什么要使用并行程序的问题前面已经进行了简单的探讨。总的来说,最重要的应该是出于两个目的。第一,为了获得更好的性能;第二,由于业务模型的需要,确实需要多个执行实体。在这里,我将更加关注于第一种情况,也就是有关性能的问题。将串行程序改造为并发程序,一般来说可以提高程序的整体性能,但是究竟能提高多少,甚至说究竟是否真的可以提高,还是一个需要研究的问题。目前,主要有两个定律对这个问题进行解答,一个是Amdahl定律,另外一个是Gustafson定律。

3.1 Amdahl定律
Amdahl定律是计算机科学中非常重要的定律。它定义了串行系统并行化后的加速比的计算公式和理论上限。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.2 Gustafson定律
在这里插入图片描述
可以看到,由于切入角度的不同,Gustafson定律的公式和Amdahl定律的公式截然不同。从.Gustafson定律中,我们可以更容易地发现,如果串行化比例很小,并行化比例很大,那么加速比就是处理器的个数。只要不断地累加处理器,就能获得更快的速度。

3.3 是否相互矛盾

在这里插入图片描述
在这里插入图片描述

4、回到JAVA:jmm

4.1 原子性
原子性是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
.比如,对于一个静态全局变量int i,两个线程同时对它赋值,线程A给它赋值1,线程B给它赋值为-1。那么不管这两个线程以何种方式、何种步调工作,i的值要么是1,要么是-1。线程A和线程B之间是没有干扰的。这就是原子性的一个特点,不可被中断。
但如果我们不使用int 型数据而使用long型数据,可能就没有那么幸运了。对于32位系统来说,long型数据的读写不是原子性的(因为long型数据有64位)。也就是说,如果两个线程同时对long 型数据进行写入(或者读取),则对线程之间的结果是有干扰的。
大家可以仔细观察一下下面的代码:
在这里插入图片描述
上述代码有4个线程对long 型数据t进行赋值,分别对t赋值为111、-999、333、444.然后,有一个读取线程读取这个t的值。一般来说,t的值总是这4个数值中的一个。这当
在这里插入图片描述

4.2 可见性
可见性是指当一个线程修改了某一个共享变量的值时,其他线程是否能够立即知道这个修改。显然,对于串行程序来说,可见性问题是不存在的。因为你在任何一个操作步骤中修改了某个变量,在后续的步骤中读取这个变量的值时,读取的一定是修改后的新值。

4.3 有序性
有序性问题可能是三个问题中最难理解的了。对于一个线程的执行代码而言,我们总是习惯性地认为代码是从前往后依次执行的。这么理解也不能说完全错误,因为就一个线程内而言,确实会表现成这样。但是,在并发时,程序的执行可能就会出现乱序。给人的直观感觉就是:写在前面的代码,会在后面执行。听起来有些不可思议,是吗?有序性问题的原因是程序在执行时,可能会进行指令重排,重排后的指令与原指令的顺序未必一致。

目录
打赏
0
0
0
0
32
分享
相关文章
利用java8 的 CompletableFuture 优化 Flink 程序
本文探讨了Flink使用avatorscript脚本语言时遇到的性能瓶颈,并通过CompletableFuture优化代码,显著提升了Flink的QPS。文中详细介绍了avatorscript的使用方法,包括自定义函数、从Map中取值、使用Java工具类及AviatorScript函数等,帮助读者更好地理解和应用avatorscript。
119 2
利用java8 的 CompletableFuture 优化 Flink 程序
课时8:Java程序基本概念(标识符与关键字)
课时8介绍Java程序中的标识符与关键字。标识符由字母、数字、下划线和美元符号组成,不能以数字开头且不能使用Java保留字。建议使用有意义的命名,如student_name、age。关键字是特殊标记,如蓝色字体所示。未使用的关键字有goto、const;特殊单词null、true、false不算关键字。JDK1.4后新增assert,JDK1.5后新增enum。
课时7:Java程序基本概念(注释)
课时7介绍了Java程序中的注释。编程语言有其语法和语义,注释有助于理解代码需求,防止断档。Java支持三类注释:单行(//)、多行(/* */)和文档注释(/** */)。注释不会被编译器编译。范例中展示了如何在代码中使用注释,并强调了注释对项目文档管理的重要性。
【YashanDB知识库】Java程序调用存储过程,在提取clob时报YAS-00004
【YashanDB知识库】Java程序调用存储过程,在提取clob时报YAS-00004
课时146:使用JDT开发Java程序
在 Eclipse 之中提供有 JDT环境可以实现java 程序的开发,下面就通过一些功能进行演示。 项目开发流程
课时5:第一个Java程序
课时5介绍了编写第一个Java程序的步骤,包括创建Hello.java文件、编写“Hello World”代码、编译和运行程序。主要内容有:1) 新建并编辑Hello.java;2) 编译Java源文件生成.class文件;3) 通过命令行解释执行Java程序;4) 解释主方法的作用及信息输出操作。本课强调了类定义、文件命名规则和基本程序结构的重要性,并建议初学者使用记事本编写代码以熟悉基础语法。
消防救援支队消防员单兵装备智能养护舱电机驱动java版程序(二)
本文探讨消防救援中智能养护舱电机驱动的Java程序设计,作为系列文章第二部分。通过自动化和智能化手段,智能养护舱提升了装备维护效率与准确性。文章详细介绍了电机驱动模块的设计与实现,包括硬件选型、PID控制策略、安全保护机制及Java程序架构,确保电机精确控制、稳定性和安全性。未来将优化功能并引入智能算法和物联网技术,进一步提升装备维护智能化水平。
消防救援支队消防员单兵装备智能养护舱点击驱动java版程序(一)
智能消防作战服架通过电机驱动系统提升消防员作业效率和安全性。本文介绍基于Java的电机驱动程序开发,涵盖硬件准备、软件环境搭建及驱动程序实现。重点包括串口通信配置、电机控制类设计与控制逻辑实现,确保电机高效稳定运行。通过正确配置通信协议和串口参数,并添加异常处理机制,保障系统的安全性和可靠性。
💡Java 零基础:彻底掌握 for 循环,打造高效程序设计
【10月更文挑战第15天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
209 63
Java 异常处理:筑牢程序稳定性的 “安全网”
本文深入探讨Java异常处理,涵盖异常的基础分类、处理机制及最佳实践。从`Error`与`Exception`的区分,到`try-catch-finally`和`throws`的运用,再到自定义异常的设计,全面解析如何有效管理程序中的异常情况,提升代码的健壮性和可维护性。通过实例代码,帮助开发者掌握异常处理技巧,确保程序稳定运行。
102 2

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等