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

相关文章
|
2天前
|
监控 Java 数据库
Java程序如何进行不停机更新?
Java程序如何进行不停机更新?
9 1
|
1月前
|
安全 Java API
16 个最常用的 Java 实用程序类
【8月更文挑战第16天】
112 1
16 个最常用的 Java 实用程序类
|
20天前
|
缓存 监控 安全
如何提高 Java 高并发程序的性能?
以下是提升Java高并发程序性能的方法:优化线程池设置,减少锁竞争,使用读写锁和无锁数据结构。利用缓存减少重复计算和数据库查询,并优化数据库操作,采用连接池和分库分表策略。应用异步处理,选择合适的数据结构如`ConcurrentHashMap`。复用对象和资源,使用工具监控性能并定期审查代码,遵循良好编程规范。
|
1月前
|
Java 编译器 开发者
Java中的异常处理是确保程序稳定性的关键
Java中的异常处理是确保程序稳定性的关键。本文探讨八大最佳实践:理解异常体系,选用恰当异常类型,提供详细错误信息,精用try-catch,善用finally块,利用try-with-resources简化资源管理,记录异常便于追踪,及避免finally中抛异常。遵循这些原则,提升代码质量和错误管理能力。
14 1
|
1月前
|
Java
"揭秘Java IO三大模式:BIO、NIO、AIO背后的秘密!为何AIO成为高并发时代的宠儿,你的选择对了吗?"
【8月更文挑战第19天】在Java的IO编程中,BIO、NIO与AIO代表了三种不同的IO处理机制。BIO采用同步阻塞模型,每个连接需单独线程处理,适用于连接少且稳定的场景。NIO引入了非阻塞性质,利用Channel、Buffer与Selector实现多路复用,提升了效率与吞吐量。AIO则是真正的异步IO,在JDK 7中引入,通过回调或Future机制在IO操作完成后通知应用,适合高并发场景。选择合适的模型对构建高效网络应用至关重要。
29 2
|
1月前
|
Java 开发者
在Java编程的广阔天地中,if-else与switch语句犹如两位老练的舵手,引领着代码的流向,决定着程序的走向。
在Java编程中,if-else与switch语句是条件判断的两大利器。本文通过丰富的示例,深入浅出地解析两者的特点与应用场景。if-else适用于逻辑复杂的判断,而switch则在处理固定选项或多分支选择时更为高效。从逻辑复杂度、可读性到性能考量,我们将帮助你掌握何时选用哪种语句,让你在编程时更加得心应手。无论面对何种挑战,都能找到最适合的解决方案。
27 1
|
1月前
|
搜索推荐 Java 程序员
在Java编程的旅程中,条件语句是每位开发者不可或缺的伙伴,它如同导航系统,引导着程序根据不同的情况做出响应。
在Java编程中,条件语句是引导程序根据不同情境作出响应的核心工具。本文通过四个案例深入浅出地介绍了如何巧妙运用if-else与switch语句。从基础的用户登录验证到利用switch处理枚举类型,再到条件语句的嵌套与组合,最后探讨了代码的优化与重构。每个案例都旨在帮助开发者提升编码效率与代码质量,无论是初学者还是资深程序员,都能从中获得灵感,让自己的Java代码更加优雅和专业。
13 1
|
1月前
|
Java
在Java编程的广阔天地中,条件语句是控制程序流程、实现逻辑判断的重要工具。
在Java编程中,if-else与switch作为核心条件语句,各具特色。if-else以其高度灵活性,适用于复杂逻辑判断,支持多种条件组合;而switch在多分支选择上表现优异,尤其适合处理枚举类型或固定选项集,通过内部跳转表提高执行效率。两者各有千秋:if-else擅长复杂逻辑,switch则在多分支选择中更胜一筹。理解它们的特点并在合适场景下使用,能够编写出更高效、易读的Java代码。
29 1
|
1月前
|
存储 算法 前端开发
JVM架构与主要组件:了解Java程序的运行环境
JVM的架构设计非常精妙,它确保了Java程序的跨平台性和高效执行。通过了解JVM的各个组件,我们可以更好地理解Java程序的运行机制,这对于编写高效且稳定的Java应用程序至关重要。
35 3
|
28天前
|
数据采集 人工智能 监控
【Azure 应用程序见解】Application Insights Java Agent 3.1.0的使用实验,通过修改单个URL的采样率来减少请求及依赖项的数据采集
【Azure 应用程序见解】Application Insights Java Agent 3.1.0的使用实验,通过修改单个URL的采样率来减少请求及依赖项的数据采集