猿创征文|深入理解高并发编程~开篇(代码片段)

哪吒 哪吒     2022-10-23     157

关键词:

目录

一、进程与线程

程序本身是静态的,是众多代码的组合产物,代码保存在文件中。如果程序要运行,则需要将程序加载到内存中,通过编译器将其编译成计算机能够理解的方式运行。
如果想启动一个Java程序,先要创建一个JVM进程。
进程是操作系统进行资源分配的最小单位,在一个进程中可以创建多个线程。多个线程各自拥有独立的局部变量、线程堆栈和程序计数器,能够访问共享的资源。

  1. 进程是操作系统分配资源的最小单位,线程是CPU调度的最小单位;
  2. 一个进程中可以包含多个线程;
  3. 进程与进程之间是相对独立的,进程中的线程之间并不完全独立,可以共享进程中的堆内存、方法区内存、系统资源等;
  4. 进程上下文的切换要比线程的上下文切换慢很多;
  5. 某个进程发生异常,不会对其它进程造成影响,但,某个线程发生异常,可能会对此进程中的其它线程造成影响;

二、线程组与线程池

1、线程组

线程组可以管理多个线程,顾名思义,线程组,就是把功能相似的线程放到一个组里,方便管理。

package com.guor.test;

public class ThreadGroupTest 
    public static void main(String[] args) 
        // 创建线程组
        ThreadGroup threadGroup = new ThreadGroup("nezha");

        Thread thread = new Thread(threadGroup,()->
            // 线程组名称
            String groupName = Thread.currentThread().getThreadGroup().getName();
            // 线程名称
            String threadName = Thread.currentThread().getName();
            System.out.println("groupName -- "+groupName);//groupName -- nezha
            System.out.println("threadName -- "+threadName);//threadName -- thread
        ,"thread");

        thread.start();
    

2、线程组和线程池有啥区别?

  1. 线程组中的线程可以跨线程修改数据,而线程组和线程组之间不可以跨线程修改数据;
  2. 线程池就是创建一定数量的线程,批量处理任务,当前任务执行完毕后,线程又可以去执行其它任务,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
  3. 线程池可以有效的管理线程的数量,避免线程的无限制创建,线程是很耗费系统资源的,动不动就会产生OOM,并且会造成cpu过度切换,也有强大的拓展功能,比如延时定时线程池。

三、用户线程与守护线程

在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 。

用户线程是最常见的线程,比如通过main方法启动,就会创建一个用户线程。

Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。

JVM中的垃圾回收、JIT编译器线程就是最常见的守护线程。

只要有一个用户线程在运行,守护线程就会一直运行。只有所有的用户线程都结束的时候,守护线程才会退出。

编写代码时,也可以通过thread.setDaemon(true)指定线程为守护线程。

Thread daemonTread = new Thread();
 
  // 设定 daemonThread 为 守护线程,默认false
 daemonThread.setDaemon(true);
 
 // 验证当前线程是否为守护线程,返回 true 则为守护线程
 daemonThread.isDaemon();

守护线程的注意事项:

  1. thread.setDaemon(true)要在thread.start()之前设置,否则会抛出IllegalThreadStateException异常。你不能把正在运行的线程设置为守护线程;
  2. 在守护线程中产生的新线程也是守护线程;
  3. 读写操作或者计算逻辑不可以设置为守护线程;

四、并行与并发

并行指当多核CPU中的一个CPU执行一个线程时,其它CPU能够同时执行另一个线程,两个线程之间不会抢占CPU资源,可以同时运行。

并发指在一段时间内CPU处理多个线程,这些线程会抢占CPU资源,CPU资源根据时间片周期在多个线程之间来回切换,多个线程在一段时间内同时运行,而在同一时刻不是同时运行的。

并行和并发的区别?

  1. 并行指多个线程在一段时间的每个时刻都同时运行,并发指多个线程在一段时间内同时运行(不是同一时刻,一段时间内交叉执行)
  2. 并行的多个线程不会抢占系统资源,并发的多个线程会抢占系统资源;
  3. 并行是多CPU的产物,单核CPU中只有并发,没有并行;

五、悲观锁与乐观锁

1、悲观锁

悲观锁在一个线程进行加锁操作后使得该对象变为该线程的独有对象,其它的线程都会被悲观锁阻拦在外,无法操作。

悲观锁的缺陷:

  1. 一个线程获得悲观锁后其它线程必须阻塞。
  2. 线程切换时要不停的释放锁和获取锁,开销巨大。
  3. 当一个低优先级的线程获得悲观锁后,高优先级的线程必须等待,导致线程优先级倒置,synchronized锁是一种典型的悲观锁。

2、乐观锁

乐观锁认为对一个对象的操作不会引发冲突,所以每次操作都不进行加锁,只是在最后提交更改时验证是否发生冲突,如果冲突则再试一遍直到成功为止,这个尝试的过程被称为自旋。乐观锁其实并没有加锁,但乐观锁也引入了诸如ABA、自旋次数过多等问题。

乐观锁一般会采用版本号机制,先读取数据的版本号,在写数据时比较版本号是否一致,如果一致,则更新数据,否则再次读取版本号,直到版本号一致。

Java中的乐观锁都是基于CAS自旋实现的。

六、CAS

1、什么是CAS?

Compare And Swap。

CAS(V, A, B) ,内存值V,期待值A, 修改值B(V 是否等于 A, 等于执行, 不等于将B赋给V)

2、CAS带来的问题

(1)ABA问题

CAS操作的流程为:

  1. 读取原值。
  2. 通过原子操作比较和替换。
  3. 虽然比较和替换是原子性的,但是读取原值和比较替换这两步不是原子性的,期间原值可能被其它线程修改。

ABA问题有些时候对系统不会产生问题,但是有些时候却也是致命的。

ABA问题的解决方法是对该变量增加一个版本号,每次修改都会更新其版本号。JUC包中提供了一个类AtomicStampedReference,这个类中维护了一个版本号,每次对值的修改都会改动版本号。

(2)自旋次数过多

CAS操作在不成功时会重新读取内存值并自旋尝试,当系统的并发量非常高时即每次读取新值之后该值又被改动,导致CAS操作失败并不断的自旋重试,此时使用CAS并不能提高效率,反而会因为自旋次数过多还不如直接加锁进行操作的效率高。

(3)只能保证一个变量的原子性

当对一个变量操作时,CAS可以保证原子性,但同时操作多个变量时CAS就无能为力了。

可以封装成对象,再对对象进行CAS操作,或者直接加锁。

七、那些年学过的锁

1、公平锁与非公平锁

  • 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
  • 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的

2、独占锁与共享锁

  • 独占锁:当多个线程则争抢锁的过程中,无论是读操作还是写操作,只能有一个线程获取到锁,其他线程阻塞等待。
  • 共享锁:允许多个线程同时获取共享资源,采取的是乐观锁的机制,共享锁限制写写操作、读写操作,但不会限制读读操作。

3、可重入锁与不可重入锁

  • 可重入锁:一个线程可以多次占用同一个锁,但是解锁时,需要执行相同次数的解锁操作;
  • 不可重入锁:一个线程不能多次占用同一个锁;

八、死锁、活锁、饿死

1、死锁

多个线程互相持有对方需要的资源,导致多个线程相互等待,无法继续执行后续任务。

2、产生死锁的4个必要条件

  1. 互斥条件:指进程对所分配到的资源进行排它性使用,在一段时间内某资源只由一个进程占用,如果此时还有其他进程请求资源,则请求者只能等待,直至占有资源的进程被释放;
  2. 请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
  3. 不可剥夺:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
  4. 循环等待:一个等待一个,产生了一个闭环。

3、饥饿

饥饿指的是线程由于无法获取需要的资源而无法继续执行。

4、产生饥饿的主要原因

  1. 高优先级的线程不断抢占资源,低优先级的线程抢不到;
  2. 某个线程一直不释放资源,导致其他线程无法获取资源;

5、如何避免饥饿

  1. 使用公平锁分配资源;
  2. 为程序分配足够的系统资源;
  3. 避免持有锁的线程长时间占用锁;

6、活锁

活锁指的是多个线程同时抢占同一个资源时,都主动将资源让给其他线程使用,导致这个资源在多个线程之间来回切换,导致线程因无法获取相应资源而无法继续执行的现象。

7、如何避免活锁

可以让多个线程随机等待一段时间后再次抢占资源,这样会大大减少线程抢占资源的冲突次数,有效避免活锁的产生。

九、多线程锁的升级原理是什么?

锁的状态总共有四种,无锁状态、偏向锁、轻量级锁、重量级锁。

随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级到重量级锁。但是锁的升级是单向的,只能升级不能降级。

1、无锁

没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功,其它修改失败的线程会不断重试直到修改成功。

无锁总是假设对共享资源的访问没有冲突,线程可以不停执行,无需加锁,无需等待,一旦发现冲突,无锁策略则采用一种称为CAS的技术来保证线程执行的安全性,CAS是无锁技术的关键。

2、偏向锁

对象的代码一直被同一线程执行,不存在多个线程竞争,该线程在后续执行中自动获取锁,降低获取锁带来的性能开销。偏向锁,指的是偏向第一个加锁线程,该线程是不会主动释放偏向锁的,只有当其他线程尝试竞争偏向锁才会被释放。

偏向锁的撤销,需要在某个时间点上没有字节码正在执行时,先暂停偏向锁的线程,然后判断锁对象是否处于被锁定状态,如果线程不处于活动状态,则将对象头设置成无锁状态,并撤销偏向锁。

如果线程处于活动状态,升级为轻量级锁的状态

3、轻量级锁

轻量级锁是指当锁是偏向锁的时候,被第二个线程B访问,此时偏向锁就会升级为轻量级锁,线程B会通过自旋的形式尝试获取锁,线程不会阻塞,从er提升性能。

当前只有一个等待线程,则该线程将通过自旋进行等待。但是当自旋超过一定次数时,轻量级锁便会升级为重量级锁,当一个线程已持有锁,另一个线程在自旋,而此时第三个线程来访时,轻量级锁也会升级为重量级锁。

注:自旋是什么?

自旋(spinlock)是指当一个线程获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。

4、重量级锁

指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。

重量级锁通过对象内部的监听器(monitor)实现,而其中monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态切换到内核态,切换成本非常高。

5、锁状态对比

偏向锁轻量级锁重量级锁
使用场景只有一个线程进入同步块虽然很多线程,但没有冲突,线程进入时间错开因而并未争抢锁发生了锁争抢的情况,多条线程进入同步块争用锁
本质取消同步操作CAS操作代替互斥同步互斥同步
优点不阻塞,执行效率高(只有第一次获取偏向锁时需要CAS操作,后面只是比对ThreadId)不会阻塞不会空耗CPU
缺点适用场景太局限。若竞争产生,会有额外的偏向锁撤销的消耗长时间获取不到锁空耗CPU阻塞,上下文切换,重量级操作,消耗操作系统资源

6、锁消除

消除锁是虚拟机另外一种锁的优化,这种优化更彻底,Java虚拟机在JIT编译时(可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁,可以节省毫无意义的请求锁时间,如StringBuffer的append是一个同步方法,但是在add方法中的StringBuffer属于一个局部变量,别切不会被其它线程所使用,因此StringBuffer不可能存在共享资源竞争的情景,JVM会自动将其锁消除。

十、Java多线程思维导图

哪吒精品系列文章:

Java学习路线总结,搬砖工逆袭Java架构师

10万字208道Java经典面试题总结(附答案)

SQL性能优化的21个小技巧

Java基础教程系列

Spring Boot 进阶实战

猿创征文|ios经典面试题之深入分析图像的解码渲染与基本原理(代码片段)

一、iOS的图片加载如下所示,加载图片的代码:-(void)imageLoad UIImage*image=[UIImageimageNamed:@"xxxxxxx"]; _imageView.image=image;UlImage是iOS中处理图像的高级类,创建一个UlImage实例只会加载DataBuffer&# 查看详情

猿创征文|ios经典面试题之深入解析分类category的本质以及如何被加载(代码片段)

一、分类的本质①Category与extensionCategory是Objective-C2.0之后添加的语言特性,Category的主要作用是为已经存在的类添加方法。extension看起来很像一个匿名的Category,但是extension和有名字的Category几乎完全是两个东西。extension... 查看详情

猿创征文|opencv编程——计算机视觉的登堂入室(代码片段)

计算机视觉是一门研究如何使机器“看”的科学,更进一步的说,就是是指用摄像头和计算机代替人眼对目标进行识别、跟踪和测量等,并进一步做图像处理,使计算机处理成为更适合人眼观察或传送给仪器检测... 查看详情

猿创征文|国产数据库tidb架构特性(代码片段)

前言TiDB是PingCAP公司自主设计、研发的开源分布式关系型数据库,是一款同时支持在线事务处理与在线分析处理(HybridTransactionalandAnalyticalProcessing,HTAP)的融合型分布式数据库产品,具备水平扩容或者缩容、金融级高可用、... 查看详情

java并发编程,深入理解reentrantlock(代码片段)

ReentrantLock简介ReentrantLock重入锁,是实现Lock接口的一个类,也是在实际编程中使用频率很高的一个锁,支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞。ReentrantLock还支持公平锁和非公... 查看详情

猿创征文|2022年我的开发者工具(代码片段)

猿创征文|2022年我的开发者工具文章目录猿创征文|2022年我的开发者工具摘要版本控制工具-GithubGithubDesktopgit-colaSSH工具-XShell7文件同步工具-Xftp7远程桌面工具-VNCViewer开发工具-Qt源码查看工具-VScode笔记工具虚拟机-VMware截... 查看详情

冰河最新出版的《深入理解高并发编程:核心原理与案例实战》到底讲了些啥?(视频为证)

大家好,我是冰河~~最近有很多小伙伴问我:《深入理解高并发编程:核心原理与案例实战》这本书有没有目录。我:安排!这不,我连夜录制了这本书的整体内容,希望能够为小伙伴们带来实质性的... 查看详情

冰河最新出版的《深入理解高并发编程:核心原理与案例实战》到底讲了些啥?(视频为证)

大家好,我是冰河~~最近有很多小伙伴问我:《深入理解高并发编程:核心原理与案例实战》这本书有没有目录。我:安排!这不,我连夜录制了这本书的整体内容,希望能够为小伙伴们带来实质性的... 查看详情

猿创征文|使用docker部署opengauss国产数据库(代码片段)

猿创征文|使用Docker部署openGauss国产数据库一、openGauss介绍1.openGauss简介2.openGauss产品定位3.支持的架构和操作系统版本二、检查宿主机环境1.检查系统版本2.检查Docker状态三、下载openGauss镜像1.进入docker官方镜像仓库2.openGauss镜像介... 查看详情

猿创征文|云原生领域之容器日常使用工具推荐(代码片段)

猿创征文|云原生领域之容器日常使用工具推荐一、云原生介绍1.云原生定义2.容器技术简介3.云原生相关工具导航二、Docker1.Docker介绍①Docker简介②Docker特点③Docker的三个基本概念④Docker的架构图2.Docker的优势3.Docker的使用效果①... 查看详情

猿创征文|java下载文件到本地目录(弹框选择)(代码片段)

需求提供一个接口,前端通过按钮下载文件,根据不同的id下载对应的文件编写接口相关代码controller/***下载文件**@paramsignId*/@GetMapping("/downloadFile")@ApiOperationSupport(order=5)@ApiOperation(value="下载文档", 查看详情

猿创征文|技术成长之路-java编程系列文件存储实践:amazons3实现文件上传下载,总结坑点,积累成长经验(代码片段)

热门系列:【Java编程系列】Minio实现文件上传下载1、前言    最近有一客户,需要独立部署文件存储到自己的私有化服务器,于是选择了让我们使用在AWS服务器的s3存储桶方式来实现。    在开发时间过程中,... 查看详情

猿创征文|技术成长之路-java编程系列文件存储实践:amazons3实现文件上传下载,总结坑点,积累成长经验(代码片段)

热门系列:【Java编程系列】Minio实现文件上传下载1、前言    最近有一客户,需要独立部署文件存储到自己的私有化服务器,于是选择了让我们使用在AWS服务器的s3存储桶方式来实现。    在开发时间过程中,... 查看详情

猿创征文|深度学习前沿应用文本审核(代码片段)

猿创征文|【深度学习前沿应用】文本审核作者简介:在校大学生一枚,C/C++领域新星创作者,华为云享专家,阿里云专家博主,腾云先锋(TDP)成员,云曦智划项目总负责人,全国高... 查看详情

猿创征文|java程序员的效率工具箱(代码片段)

一、前言CSDN猿创征文第四季寻找开发者每天都在使用、寻找、贡献、创作各类开发者工具,包括开源服务、付费软件、API等。作为Java程序员,常用的软件开发软件(如IDEA、Eclipse)、开源项目(SpringBoot、MyBati... 查看详情

猿创征文|java程序员的效率工具箱(代码片段)

一、前言CSDN猿创征文第四季寻找开发者每天都在使用、寻找、贡献、创作各类开发者工具,包括开源服务、付费软件、API等。作为Java程序员,常用的软件开发软件(如IDEA、Eclipse)、开源项目(SpringBoot、MyBati... 查看详情

猿创征文|再见宽表,新一代bi要用dql了(代码片段)

BI商业智能这个概念已经提出好几十年了,这个概念本身比较宽泛,不同人也有不同的理解和定义,但落实到技术环节,特别是面向业务用户的环节,所称的BI,基本就是指的多维分析或者自助报表不管是... 查看详情

猿创征文|promethues入门,看懂不会写(代码片段)

1、Promethues架构官方网站:Grafana|Prometheus1)Prometheusserver可定期从活跃的(up)目标主机上(target)拉取监控指标数据,目标主机的监控数据可通过配置静态job或者服务发现的方式被prometheusserver采集到&#x... 查看详情