当前位置: 首页 > article >正文

【思维导图】并发编程

学习计划:将目前已经学的知识点串成一个思维导图。在往后的学习过程中,不断往思维导图里补充,形成自己整个知识体系。对于思维导图里的每个技术知识,自己用简洁的话概括出来, 训练自己的表达能力。

并发和并行的区别
并发是指多个任务交替执行。
并行是指多个任务同时被执行。 

线程与进程的区别
1、进程包含线程。
2、线程之间可以共享数据,比如说java线程之间共享堆内存和方法区里的数据。而进程之间是独立的,默认情况下是不共享数据的。
3、线程上下文切换开销小,而进程上下文切换开销大。(当线程状态改变时,就会出现线程上下文切换。线程上下文切换涉及到用户态与内核态的转变,因此开销较大。)
 


java线程与操作系统的线程的区别
java线程本质上就是操作系统的内核级线程,在一对一模型中每个java线程都会映射到一个内核级线程中。因此线程切换会导致用户态向内核态的转变,但不会因为线程阻塞而导致其他线程都阻塞。
而早期的java线程属于用户级线程,线程切换不会触发用户态的转变,但是如果有一个线程被阻塞,那么所有线程都会被阻塞。

线程生命周期
1、创建态(NEW)
线程被创建后但还没有调用start方法就是创建态。
2、运行态(RUNNABLE)
运行态包含就绪和运行两种状态。
3、阻塞态(BLOCKED)
当线程获取锁失败就会进入阻塞状态。
4、等待状态(WAITING)
调用wait方法就会进入等待状态,需要其他线程唤醒才能进入运行态。
5、超时等待状态(TIME_WAITING)
在等待状态的基础上增加了超时限制。当超时时间到达后,就会自动进入运行态。
6、终止态(DEAD)
线程执行run方法结束后或者异常退出后,线程就处于终止态了。
 


调用start方法和直接调用run方法的区别
调用start方法才表示启动一个线程并进入就绪态;而如果是调用run方法的话,并不会开启一个线程,而是会在main线程下执行这个run方法。 

创建多线程的几种方式
1、继承Thread类的方式
2、实现Runnable接口的方式
3、实现Callable接口
4、使用线程池创建线程

什么是线程安全
线程安全问题是指当多个线程同时读写一个共享资源并且没有任何同步措施时,导致出现脏数据或者其他不可预见的结果的问题。
 

如何保证线程安全
1、数据单线程内可见

2、只读对象

3、使用线程安全类

4、线程同步机制

5、共享变量可见性、禁止指令重排

synchronized和Lock的区别及使用场景
- 用法上分析:
1、synchronized锁是隐式锁,由jvm帮我们加锁释放锁;而Lock锁是显式的加锁和释放锁。所以synchronized锁更加便捷,但是因为它加锁和释放锁是固定的,因此Lock锁的会更加灵活。
- 功能上分析:
2、Lock锁支持中断,获取锁失败等待重新获取锁的线程如果被中断,会抛出异常,并且返回。而synchronized是如果线程获取锁失败会一直被阻塞,等待获取锁。
3、Lock锁支持超时获取锁,在等待重新获取锁过程中如果超过指定时间,则直接返回。这样可以避免死锁问题。而synchronized没有这样的功能。
4、Lock锁支持公平锁,比如ReentrantLock就实现了公平锁。而synchronized只有非公平锁。
适用场景:
1、synchronized适用于简单的需要加锁的逻辑中,而Lock适用于比较复杂的代码中。
2、如果有需要中断、保证公平的情景下应该选用Lock锁。
 


synchronized用法
synchronized可以修饰代码块或普通方法或静态方法。
 
synchronized锁静态方法和普通方法的区别
synchronized修饰静态方法,那么获取的对象锁就是当前类的Class对象。
synchronized修饰普通方法,那么获取的对象锁就是当前实例对象。

synchronized原理
synchronized底层是基于monitor实现的。使用synchronized时会尝试获取代码块或同步方法所属对象的monitor,而这底层是通过字节码指令monitorenter和monitorexit实现的。当执行monitorenter时,就会尝试获取monitor,只有monitor为0或同一个线程再次获取同一把锁则获取锁成功。只有获取成功才能执行代码块或同步方法里的代码。
 


synchronized怎么做到可重入的,说场景
当线程尝试获取锁时,判断出monitor的owner就是自己本身这个线程,那么会让monitor计数器继续+1,这样就实现了可重入。
 


synchronized锁升级过程
synchronized引入锁升级过程,是为了提高获取锁和释放锁的性能,毕竟在jdk1.6之前如果线程竞争没获取锁的话就会进入阻塞态,这需要进行内核态的转变,性能差。于是引入了偏向锁、量级锁的升级过程。偏向锁就是当一个线程想要获取锁时,会在对象头中存储这个线程id并且将对象头里的偏向锁标识设置为1,如果下次同一个线程想要再次获取同一把锁时,首轻先判断的就是对象头中的线程id是否与自己的相同,如果相同,则这个线程直接获取锁成功。这样就省去了CAS加锁的操作。而如果不同的线程想要获取这把锁,则会将这个偏向锁升级为轻量级锁。轻量级锁加锁过程就是CAS将对象头的mark word信息复制到线程的栈中,然后mark word里的指针指向锁记录。而如果CAS失败,则自旋尝试重新获取锁。而当线程竞争激烈时,就会升级为重量级锁。重量级锁就是一旦没有获取到锁,就被阻塞。
 


什么是悲观锁和乐观锁
悲观锁认为自己在使用数据时一定会有其他线程来修改数据,因此在获取数据前会先加锁,确保数据不会被其他线程修改。乐观锁认为自己在使用数据时不会有其他线程来修改数据,因此在读取数据时不会添加锁,只有修改数据前数据是否有被其他线程修改,如果没有其他线程修改,那么就放心大胆的修改数据,而如果数据被其他线程修改,那么自己就修改数据失败。

什么是CAS
CAS是实现乐观锁的一种算法。CAS能够在不使用锁的情况下,保证线程同步。
CAS就是首先读取数据,当要修改数据时会再次读取数据,检查数据是否有发生变化,如果数据没变化才能更新数据。
 


CAS有什么缺点,怎么解决
1、ABA问题
当更新数据前检查数据有没有发生变化,如果没有发生变化,但是这个数据可能经历了由A变成B然后变成A的过程。像这种CAS检查没有发生变化,但是实际上发生变化了。解决方法就为变量追加版本号,当变量发生变化时,就会有一个新的版本号。这样当CAS检查数据是否有变化时,不仅检查数据是否有发生变化,将版本号一起检查是否有发生变化。
2、循环时间开销大
如果CAS一直不成功,则会一直自旋,给CPU带来非常大的开销。
3、只能保证一个共享变量的原子操作
CAS只能保证一个共享变量进行原子操作,但是不支持多个共享变量的原子操作。这只能使用锁来保证。但也可以将多个变量放到一个对象里用CAS保证原子性。

哪些结构用了CAS
1、ReentrantLock的实现是通过使用CAS来获取锁。
2、AtomicInteger、AtomicLong、AtomicReference 和 AtomicBoolean 等类都通过 CAS 实现了原子性的加、减、更新等操作。
3、ConcurrentHashMap 在更新哈希桶中的元素时,使用 CAS 来避免锁的竞争,提高性能。

公平锁与非公平锁的区别
公平锁是指多个线程按照申请锁的顺序来获取锁,线程会被加入到一个队列中,只有队列中的第一个线程才能获取锁。而非公平锁是加锁时就直接尝试获取锁。非公平锁可能出现后申请锁先获取锁的场景,因此非公平锁可能出现饿死的问题。但是非公平锁可以减少唤醒线程的上下文切换的开销,整体效率更高。
 


怎么创建一个公平锁
使用ReentrantLock的有参构造器,传一个true构造的公平锁。
 
 


AQS是干什么的?AQS的原理?
AQS是一个java并发包里的一个抽象类,里面提供了关于构建锁的各种方法,比如ReentrantLock、Semaphore这些锁就是基于AQS实现的。我们也可以基于AQS自定义锁,我们只需要提供获取锁、释放锁逻辑,剩余的都交给AQS处理就行了。
AQS的原理就是维护了一个volatile修饰的state变量表示同步状态。然后通过CAS完成对state变量的修改。AQS还维护了一个队列用来存储获取锁失败的线程,这些线程在一个死循环里,只有当前驱结点是头节点才能尝试获取锁,因此队列里的线程会先被阻塞进入等待状态,直到前驱结点将其唤醒后,检查前驱结点是头结点后再尝试获取锁。这样可以避免不断死循环判断所带来资源浪费。
 
 

AQS里的node你了解吗?
node结构里包含了这个node所对应的线程id、线程状态、前驱节点、后继结点等信息。如果没有获取锁的线程会被包装成一个node结构存储在AQS维护的队列中。node结点的前驱结点的线程状态决定线程是否被阻塞,线程被阻塞后等待前驱结点的唤醒后重新获取锁,这样可以避免不断循环获取锁所带来资源浪费。

ReentrantLock是怎么实现可重入的?
(ReentrantLock中的tryAcquire方法体现可重入)
可重入性就是指同一个线程可以多次获取同一把锁。
ReentrantLock维护了一个state变量,尝试获取锁逻辑中,如果state变量为0,那么就可以获取锁,并将state设置为1,表示锁已经被这个线程占用了;如果state变量不为0,那么就会判断当前线程和持有这个锁的线程是不是同一个,如果是同一个,则会让state变量继续+1,然后返回true表示获取锁成功。如果锁被获取了n次,那么就需要释放n次,前n-1次释放返回的是false;只有当state被减为0时,才算真正释放成功,返回true。

synchronized和ReentrantLock的区别和使用场景
1、synchronized是隐式锁,由jvm加锁和释放锁;ReentrantLock是显式锁。因此synchronized使用其他比较便捷,但ReentrantLock更加灵活。
2、ReentrantLock支持中断,获取锁失败后等待获取锁期间可以被中断,而synchronized不支持中断,获取锁失败后会一直被阻塞直到锁被释放。
3、ReentrantLock支持超时获取锁,如果等待获取锁时间超过了指定时间会直接返回,而不是继续等待获取锁。
4、ReentrantLock支持公平锁和非公平锁,而synchronized只支持非公平锁。
创建线程池的方式
1)使用Executors类中提供的线程池。
2)自己使用ThreadPoolExecutor构造器创建一个线程池(自己new一个ThreadPoolExecutor对象)

线程池核心参数
1)核心线程数(corePoolSize)
2)线程池最大线程数(maximumPoolSize)
3)线程池除核心线程以外的线程的空闲存活时间(keepAliveTime)
4)阻塞队列(BlockingQueue<Runnable> workQueue)
5)线程工厂(ThreadFactory threadFactory)
6)拒绝策略(RejectedExecutionHandler handler)
线程池类型
1)fixed线程池,表示创建固定线程数量的线程池(Executors.newFixedThreadPool)
2)single线程池,创建只有一个线程的线程池(Executors.newSingleThreadExecutor)
3)Cached线程池,表示创建线程数可伸缩的线程池,最大线程数可以达到Integer.MAX_VALUE。
4)Schedule线程池,与Cached线程池的区别在于不回收空闲线程。 
 

自定义线程工厂
通过实现ThreadFactory接口实现自定义线程工厂,之所以推荐自定义线程工厂的原因在于,为线程指定有意义的名称,怎么指定?就是在重写newThread方法时,创建线程传入线程名称参数即可。

线程池处理任务流程
当要执行任务时,首先获取线程池里当前运行线程数,判断当前运行线程数是否大于等于核心线程数,如果当前运行线程数小于核心线程数,则会创建一个线程执行这个任务;如果当前工作线程数大于等于核心线程数,则会进入下一个流程,判断阻塞队列是否已满,如果没满,则将这个任务添加到阻塞队列中;如果阻塞队列已满,则会进入下一个流程,判断当前运行线程数是否大于等于最大线程数,如果当前运行线程数小于最大线程数,那么就会新建一个线程执行这个任务;反之,拒绝这个任务。(execute方法)
 


如果让线程池工作的话,需要Worker,Worker中存储什么信息
线程池里的线程用Worker类来实现。Woker类中有一个Thread字段和firstTask表示这个线程被start后执行的第一个任务。 这个线程执行完这个firstTask后就会循环从阻塞队列里获取任务执行。 

线程池拒绝任务方式 
1)AbortPolicy:默认情况下的拒绝策略就是丢弃这个任务并抛出异常
2)DiscardPolicy:丢弃这个任务,不做任何处理
3)DiscardOldestPolicy:抛弃队列中等待最久的任务,并执行当前任务。
4)CallerRunsPolicy:用调用者所在线程来执行任务。
5)我们也可以通过实现RejectedExecutionHandler接口自定义策略。比如说将拒绝的任务持久化到数据库中,等有余力时再处理这些未被处理的任务。


http://www.kler.cn/a/527437.html

相关文章:

  • 使用大语言模型在表格化网络安全数据中进行高效异常检测
  • 创新创业计划书|建筑垃圾资源化回收
  • python-leetcode-旋转链表
  • 进阶数据结构——高精度运算
  • QT设置应用程序图标
  • Flutter使用Flavor实现切换环境和多渠道打包
  • 答疑解惑:如何监控EMC unity存储系统磁盘重构rebuild进度
  • 实战:利用百度站长平台加速网站收录
  • Agent 高频知识汇总:查漏补缺参考大全
  • 大模型本地化部署(Ollama + Open-WebUI)
  • 《TCP 网络编程实战:开发流程、缓冲区原理、三次握手与四次挥手》
  • 【4Day创客实践入门教程】Day1 工具箱构建——开发环境的构建
  • 数据包的发送流程
  • Linux命令汇总
  • 力扣017_最小覆盖字串题解----C++
  • AI学习指南HuggingFace篇-Datasets 库入门
  • [EAI-028] Diffusion-VLA,能够进行多模态推理和机器人动作预测的VLA模型
  • 研发的护城河到底是什么?
  • 双指针c++
  • 5.4.1 结构化分析方法
  • Golang 并发机制-3:通道(channels)机制详解
  • 【C/C++】区分0、NULL和nullptr
  • 26.Word:创新产品展示说明会【9】
  • Keepalived 安装
  • 基于微信小程序的实习记录系统设计与实现(LW+源码+讲解)
  • DeepSeek的崛起与OpenAI的守擂:AI大模型时代的竞争新格局