首页 > 编程语言 >《实战Java高并发程序——第1章 走入并行世界》

《实战Java高并发程序——第1章 走入并行世界》

时间:2023-11-12 10:36:38浏览次数:40  
标签:Java 并行 并发程序 并发 线程 走入 执行 CPU

  • 基础平台Java虚拟机,虚拟机除了要执行main函数外,还需要做JIT编译和垃圾回收。无论是main函数、JIT编译还是垃圾回收,在虚拟机内部都是一个单独的线程。
  • 多核CPU:将多个独立的计算单元整合到单独的CPU中
  • 如何让多个CPU内核有效并正确地工作也就成了一门技术。如多线程间如何保证线程安全,如何正确理解线程间的无序性、可见性。如何尽可能地设计并行程序,如何将串行程序改造为并行程序等等。

必须知道的几个概念

服务端编程还是需要大量并行计算的,而Java也主要占领着服务端市场,那么对Java并行计算的研究就显得非常重要。

同步(Synchronous)和异步(Asynchronous)

同步和异步通常用来形容一次方法调用

同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。

异步方法调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。而异步方法通常会在另外一个线程中真实地执行。整个过程不会阻碍调用者的工作。对于调用者来说,异步方法调用似乎是一瞬间就完成的。如果异步方法调用需要返回结果,那么当这个异步调用真实完成时,则会通知调用者。

并发(Concurrency)与并行(Parallelism)

并发和并行是两个非常容易混淆的概念。它们都可以表示两个或者多个任务一起执行,但是侧重点有所不同。

单核CPU下,线程实际还是串行执行的。操作系统中有一个组件叫做任务调度器,将CPU的时间片分给不同的程序使用,只是由于CPU在线程间切换非常快,人类感觉是同时运行的。
一般会将这种线程轮流使用CPU的做法成为并发,并发偏重于多个任务交替执行,而多个任务之间有可能是串行的
image

在多核CPU下,每个核都可以调度运行线程,这时候线程是可以并行的。
image
image

  • 并发(concurrent)是同一时间应对多件事情的能力
    • 并发是指在一个时间段内,多个任务交替执行的能力。这些任务可以是同时启动的,但它们不一定同时执行。在并发中,任务之间可能会交错执行,每个任务都分配一些时间片段来执行。
  • 并行(parallel)是同一时间多件事情的能力(真正意义上的"同时执行")
    • 并行是指多个任务在同一时刻实际上同时执行的能力,通常需要多个处理单元(例如多核处理器)或多台计算机来实现。

实际上,如果系统内只有一个CPU,而使用多进程或者多线程任务,那么真实环境中这些任务不可能是真实并行的,毕竟一个CPU一次只能执行一条指令,在这种情况下多进程或者多线程就是并发的,而不是并行的(操作系统会不停地切换多个任务)。真实的并行也只可能出现在拥有多个CPU的系统中(比如多核CPU)。

临界区

临界区用来表示一种公共资源或者说共享数据,可以被多个线程使用。但是每一次只能有一个线程使用它,一旦临界区资源被占用,其他线程想要使用这个资源就必须等待

举例:办公室中的打印机。

在并行(或并发)程序中,临界区资源是保护的对象

阻塞(Blocking)和非阻塞(Non-Blocking)

阻塞和非阻塞通常用来形容多线程间的相互影响。比如一个线程占用了临界区资源,那么其它所有需要这个资源的线程就必须在这个临界区中等待。等待会导致线程挂起,这种情况就是阻塞
非阻塞的意思与之相反,它强调没有一个线程可以妨碍其他线程执行,所有的线程都会尝试不断向前执行。

死锁(Deadlock)、饥饿(Starvation)、活锁(Livelock)

死锁指当每个进程都在等待某个资源被释放,而无法继续执行时,就会导致死锁。

死锁的四个必要条件:
1. 互斥条件:一个资源每次只能被一个进程使用;
2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放;
3. 不剥夺条件: 进程已获得的资源,在未使用完之前,不能强行剥夺;
4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系;

饥饿是指某一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行。比如,它的线程优先级可能太低,而高优先级的线程不断抢占它需要的资源,导致低优先级线程无法工作。
与死锁相比,饥饿还是有可能在未来一段时间内解决的。(比如高优先级的线程已经完成任务,不再疯狂执行)。

活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败,从而陷入一种似乎被锁住的状态。

举例:两个人面对面走来,你向右走,对方也向右走。

并发级别(了解)

由于临界区的存在,多线程之间的并发必须受到控制。根据控制并发的策略,我们可以把并发的级别分为阻塞、无饥饿、无障碍、无锁、无等待等。

有关并行的两个重要定律(了解)

为什么要使用并行程序:

  • 为了获得更好的性能
  • 由于业务模型的需要,确实需要多个执行的实体

Amdahl定律

根据Amdahl定律,使用多核CPU对系统进行优化的效果取决于CPU的数量,以及系统中的串行代码比例。CPU数量越多,串行化比例越低,则效果越好。仅增加CPU数量而不降低程序的串行化比例,无法提高系统性能。

Gustafson定律

从Gustafson定律中,我们可以更容易地发现,如果串行化比例很小,并行化比例很大,那么加速比就是处理器的个数。只要增加处理器,就能获得更快的速度。

Java内存模型(JMM)

JMM的关键技术点都是围绕着多线程的原子性可见性有序性来建立的。

原子性(Atomicity)

原子性是指一个操作是不可中断的。即使在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
比如,对于一个静态全局变量int i,两个线程同时对它赋值,线程A给它赋值为1,线程B给它赋值为-1。那么不管这两个线程以何种方式、何种步调工作,i的值要么是1,要么是-1。线程A和线程B之间是没有干扰的。这就是原子性的一个特点——不可被中断。
但如果我们不使用int型数据而使用long型数据,可能就没有那么幸运了。对于32位系统来说,long型数据的读写不是原子性的(因为long型数据有64位)。也就是说,如果两个线程同时对long型数据进行写入(或者读取),则线程之间是有干扰的。

可见性(Visibility)

可见性是指当一个线程修改了某个共享变量的值时,其他线程是否能够立即知道这个修改

Java提供了volatile关键字来保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。

有序性(Ordering)

有序性问题的原因是程序在执行时,可能会进行指令重排。

在Java里面,可以通过volatile关键字来保证一定的“有序性”(具体原理在下一节讲述)。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。当然JMM是通过Happens-Before 规则来保证有序性的。

不过这里还需要强调一点,对于一个线程来说,它看到的指令执行顺序一定是一致的(否则应用根本无法正常工作)。也就是说指令重排是有一个基本前提的,就是保证串行语义的一致性。指令重排不会使串行语义逻辑发生问题。因此,在串行代码中,大可不必担心。

happens-before原则

前文已经介绍了指令重排,虽然Java虚拟机和执行系统会对指令进行一定的重排,但是指令重排是有原则的,并非所有的指令都可以随便改变执行位置。以下罗列了一些基本原则,这些原则是指令重排不可违背的
· 程序顺序原则:在一个线程内保证语义的串行性。
· volatile规则:volatile变量的写先于读发生,这保证了volatile变量的可见性。
· 锁规则:解锁(unlock)必然发生在随后的加锁(lock)前。
· 传递性:A先于B, B先于C,那么A必然先于C。
· 线程的start()方法先于它的每一个动作
· 线程的所有操作先于线程的终结(Thread.join())
· 线程的中断(interrupt())先于被中断线程的代码
· 对象的构造函数的执行、结束先于finalize()方法

标签:Java,并行,并发程序,并发,线程,走入,执行,CPU
From: https://www.cnblogs.com/keyongkang/p/17826819.html

相关文章

  • java基础
    java基础注释单行://多行:/**/文档:/***/标识符关键字:java所有的组成都需要名字.le类名、变量名、以及方法名都被成为标识符标识符注意点:所有的标识符都应该一字母A-Z,灭源符号或者下划线开始不能使用关键字作为变量名或者方法名标识符是大小写敏感的首字符之后......
  • Java之集合及其练习
     1.ArrayList集合和数组的优势对比:长度可变添加数据的时候不需要考虑索引,默认将数据添加到末尾1.1ArrayList类概述什么是集合提供一种存储空间可变的存储模型,存储的数据容量可以发生改变ArrayList集合的特点长度可以变化,只能存储引用数据类型。泛型的使用用于约束集合中存储元素......
  • java ArrayList的基本使用
    packagecom.elaina.test1;importjava.util.ArrayList;publicclasstest1{publicstaticvoidmain(String[]args){//1.创建集合的对象//泛型:限定集合中的存储数据的类型//ArrayList<String>list=newArrayList<String>();......
  • 在Java中,JAR和WAR之间的区别
    内容来自DOChttps://q.houxu6.top/?s=在Java中,JAR和WAR之间的区别一个.jar文件和一个.war文件之间有什么区别?仅仅是文件扩展名吗?还是还有其他的东西?来自Java小贴士:ear、jar和war文件的区别:这些文件只是使用javajar工具进行压缩的文件。这些文件被用于不同的目的。下面......
  • Java学习—JAVA正则表达式
    -----生命如同寓言,其价值不在于长短,而在于内容。正则表达式定义了字符串的模式。正则表达式可以用来搜索、编辑或处理文本。正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别。1、正则表达式语法元字符描述\将下一个字符标记符、或一个向后引用、或一个八进制转义符。......
  • 使用反编译软件jd-gui.exe,打开提示:The application requires a Java Runtime Enviro
      jd-gui.exe,打开提示:TheapplicationrequiresaJavaRuntimeEnvironment1.8.0 但是已经是java1.8版本了 这时候修改注册表win+R输入regedit打开注册表找到HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\JavaRuntimeEnvironment\1.8如果 JavaRuntimeEnvironment......
  • 深入了解Java进程和线程
    Java是一种流行的编程语言,广泛用于开发各种应用程序,从桌面应用到服务器端应用。在Java编程中,进程和线程是两个关键概念,它们对于实现多任务处理和并发性非常重要。本文将深入探讨Java中的进程和线程,以及如何使用它们来构建高效的应用程序。什么是进程?在Java中,进程是一个独立的执行环......
  • 每天5道Java面试题(第9天)
    1. HashMap和Hashtable有什么区别?存储:HashMap允许key和value为null,而Hashtable不允许。线程安全:Hashtable是线程安全的,而HashMap是非线程安全的。推荐使用:在2. 如何决定使用HashMap还是TreeMap?对于在Map中插入、删除、定位一个元素这类操作,HashMap是最好的选择,因......
  • Java登陆第四天——SQL之DQL(二)
    分页查询现实总是有很多数据,多数情况都需要用分页显示数据。(很多数据显示在一个页面不太现实。)--关键字LIMIT--索引总是从0开始的,页面大小为一页显示多少条数据select列名from表名limit索引,页面大小;栗子:按照user_id降序排列,每页显示3条数据。SQL语句:select*fr......
  • 如何从 javascript 中的 Web api 将 json 对象传递到项目的其余部分
    在将从WebAPI获取的JSON数据传递到项目的其他部分之前,您需要确保在完成fetch请求并获得响应后再进行处理。因为fetch是一个异步操作,需要等待服务器响应的完成才能获取到数据。在您的代码示例中,您可以将json数据传递给其他函数或组件进行后续处理。以下是一种可能的处理方式:fetch(B......