java多线程并发系列--基础知识点(笔试、面试必备)(下)

简介: 多个线程间锁的并发控制,对象锁多个线程、每个线程持有该方法所属对象的锁以及类锁。synchronized, wait, notify 是任何对象都具有的同步工具

13 多个线程间锁的并发控制


多个线程间锁的并发控制,对象锁多个线程、每个线程持有该方法所属对象的锁以及类锁。synchronized, wait, notify 是任何对象都具有的同步工具


对象锁的同步和异步


  • 同步:synchronized,同步的概念就是共享,只需要针对共享的资源,才需要考虑同步。
  • 异步:asynchronized,异步的概念就是独立,相互之间不受到任何制约。


同步的目的就是为线程安全,其实对于线程安全来说,需要满足两个特性:原子性(同步)、可见性。


14 Volatile关键字


Volatile作用,实现变量在多个线程间可见,保证内存可见性和禁止指令重排


多线程的内存模型:main memory(主存)、working


memory(线程栈),在处理数据时,线程会把值从主存load到本地栈,完成操作后再save回去(volatile关键词的作用:每次针对该变量的操作都激发一次load and save)。


25.png


15 ThreadLocal


线程局部变量,以空间换时间的手段,为每个线程提供变量的独立副本,以无锁的情况下保障线程安全。主要解决的就是让每个线程执行完成之后再结束,这个时候就要用到join()方法。


适用场景:


  • 在并发不是很高的时候,加锁的性能会更好
  • 在高并发量场景下,使用ThreadLocal可以在一定程度上减少锁竞争。


16 多线程同步和互斥实现方法


1). 线程同步,是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。


 线程间的同步方法,大体可分为两类:用户模式和内核模式。顾名思义:


内核模式,就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态。内核模式下的方法有:


  • 事件
  • 信号量
  • 互斥量


用户模式,就是不需要切换到内核态,只在用户态完成操作。用户模式下的方法有:


  • 原子操作(例如一个单一的全局变量)
  • 临界区


2). 线程互斥,是指对于共享的进程系统资源,在各单个线程访问时的排它性。


当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。 线程互斥可以看成是一种特殊的线程同步。


17 线程之间通信


线程是操作系统中独立的个体,但这些个体之间如果不经过特殊的协作就不能成为一个整体,线程间的通信就成为整体的必用方式之一。


线程间通信的几种方式?


线程之间的通信方式:


  • 共享内存
  • 消息传递


共享内存:在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信。典型的共享内存通信方式,就是通过共享对象进行通信。


26.png


消息传递:在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信。在 Java 中典型的消息传递方式,就是 wait()notify() ,或者 BlockingQueue 。


27.png


18 什么是 Java Lock 接口?


java.util.concurrent.locks.Lock 接口,比 synchronized 提供更具拓展行的锁操作。它允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。它的优势有:


  • 可以使锁更公平。
  • 可以使线程在等待锁的时候响应中断。
  • 可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间。
  • 可以在不同的范围,以不同的顺序获取和释放锁。


28.png


19 Java AQS


AQS ,AbstractQueuedSynchronizer ,即队列同步器。它是构建锁或者其他同步组件的基础框架(如 ReentrantLock、ReentrantReadWriteLock、Semaphore 等),J.U.C 并发包的作者(Doug Lea)期望它能够成为实现大部分同步需求的基础。它是 J.U.C 并发包中的核心基础组件。


 优势:


AQS 解决了在实现同步器时涉及当的大量细节问题,例如获取同步状态、FIFO 同步队列。基于 AQS 来构建同步器可以带来很多好处。它不仅能够极大地减少实现工作,而且也不必处理在多个位置上发生的竞争问题。


在基于 AQS 构建的同步器中,只能在一个时刻发生阻塞,从而降低上下文切换的开销,提高了吞吐量。同时在设计 AQS 时充分考虑了可伸缩性,因此 J.U.C 中,所有基于 AQS 构建的同步器均可以获得这个优势。


20 同步类容器


何为同步容器?可以简单地理解为通过synchronized来实现同步的容器,如果有多个线程调用同步容器的方法,它们将会串行执行。


 特点:


  • 是线程安全的
  • 某些场景下可能需要加锁来保护复合操作


 常见同步类容器:


  • 如Vector、HashTable
  • 使用JDK的Collections.synchronized等工厂方法去创建实现的。
  • 底层是用传统的synchronized关键字对方法进行同步。
  • 无法满足高并发场景下的性能需求


21 并发类容器


jdk5.0以后提供了多种并发类容器来替代同步类容器从而改善性能。


同步类容器局限性:


  • 都是串行化的。
  • 虽实现了线程安全,却降低了并发性
  • 在多线程环境时,严重降低了应用程序的吞吐量。


常用的并发类容器:


  • ConcurrentHashMap
  • Copy-On-Write容器


 ConcurrentHashMap原理


  • 内部使用段(Segment)来表示这些不同的部分
  • 每个段相当于一个小的HashTable,它们有自己的锁。
  • 把一个整体分成了16个段(Segment)。也就是最高支持16个线程的并发修改操作。
  • 这也是在多线程场景时通过减小锁的粒度从而降低锁竞争的一种方案。


 Copy-On-Write容器


Copy-On-Write简称COW,是一种用于程序设计中的优化策略。


  • 读写分离,读和写分开
  • 最终一致性
  • 使用另外开辟空间的思路,来解决并发冲突


 JDK里的COW容器有两种:


  • CopyOnWriteArrayList:适用于读操作远远多于写操作的场景,例如,缓存.
  • CopyOnWriteArraySet:线程安全的无序的集合,可以将它理解成线程安全的HashSet,适用于Set 大小通常保持很小,只读操作远多于可变操作,需要在遍历期间防止线程间的冲突


22 并发Queue


 并发Queue:


  • ConcurrentLinkedQueue,高性能队列,当许多线程共享访问一个公共集合时,ConcurrentLinkedQueue 是一个恰当的选择。
  • BlockingQueue,阻塞队列,是一个支持两个附加操作的队列,常用于生产者和消费者的场景。


 ConcurrentLinkedQueue


  • 适用于高并发场景
  • 使用无锁的方式,实现了高并发状态下的高性能
  • 其性能好于BlockingQueue
  • 遵循先进先出的原则


常用方法:


  • Add()和offer()都是加入元素的方法
  • Poll()和peek()都是取头元素节点,区别在于前者会删除元素,后者不会。


 BlockingQueue接口实现:


  • ArrayBlockingQueue:基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长的数组,以便缓存队列中的数据对象,其内部没实现读写分离,也就意味着生产和消费者不能完全并行,适用很多场景。


  • LinkedBlockingQueue:基于链表的阻塞队列,同ArrayBlockingQueue类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),LinkedBlockingQueue之所以能够高效地处理并发数据,是因为其内部实现采用分离锁(读写分离两个锁),从而实现生产者和消费者操作完全并行运行。


  • PriorityBlockingQueue:基于优先级别的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定,也就是说传入队列的对象必须实现Comparable接口),在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁


  • DelayQueue:带有延迟时间的Queue,其中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue中的元素必须先实现Delayed接口,DelayQueue是一个没有大小限制的队列,应用场景很多,比如对缓存超时的数据进行移除、任务超时处理、空闲连接的关闭等等。


  • SynchronousQueue:一种没有缓冲的队列,生产者产生的数据直接会被消费者获取并且立刻消费


23 多线程的设计模式


  • 基于并行设计模式演变而来
  • 属于设计优化的一部分
  • 是对一些常用的多线程结构的总结和抽象
  • 常见的多线程设计模式有哪些?


24 Concurrent.util常用类


CountDownLatch: 用于监听某些初始化操作,等初始化执行完毕后,通知主线程继续工作。


CycilcBarrier: 所有线程都准备好后,才一起出发,只要有一个人没有准备好,大家都等待。


Concurrent.util常用类定义:实现异步回调,jdk针对该场景提供了一个实现的封装,简化了调用 适合场景:处理耗时的业务逻辑时,可有效的减少系统的响应时间,提高系统的吞吐量。


Concurrent.util常用类Semaphore:信号量,适合高并发访问, 用于进行访问流量的控制


ReentrantLock(重入锁)重入锁,在需要进行同步的代码部分加上锁定,但不要忘记最后一定要释放锁定,不然会造成锁永远无法释放,其他线程永远也进不来的结果。


 锁与等待/通知


  • 多线程间进行协作工作则需要Object的wait()和notify()、notifyAll()方法进行配合工作
  • 在使用锁的时候,可以使用一个新的等待/通知的类,它就是Condition
  • 这个Condition是针对具体某一把锁的


 多Condition


  • 可通过一个Lock对象产生多个Condition进行多线程间的交互
  • 使得部分需要唤醒的线程唤醒,其他线程则继续等待通知。


 ReentrantReadWriteLock(读写锁)


  • 其核心就是实现读写分离的锁。尤其适应在在高并发访问下读多写少的情况下,性能要远高于重入锁。
  • 将读写锁分离为读锁和写锁
  • 在读锁下,多个线程可以并发进行访问
  • 在写锁下,只能一个一个的顺序访问
  • 锁优化


25 线程池


29.png


 使用 Executor 框架的原因:


  • 每次执行任务创建线程 new Thread() 比较消耗性能,创建一个线程是比较耗时、耗资源的。
  • 调用 new Thread() 创建的线程缺乏管理,被称为野线程,而且可以无限制的创建,线程之间的相互竞争会导致过多占用系统资源而导致系统瘫痪,还有线程之间的频繁交替也会消耗很多系统资源。
  • 接使用 new Thread() 启动的线程不利于扩展,比如定时执行、定期执行、定时定期执行、线程中断等都不便实现


 线程池的创建方式:


普通任务线程池


  • newFixedThreadPool(int nThreads) 方法,创建一个固定长度的线程池。


  • 每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程规模将不再变化。
  • 当线程发生未预期的错误而结束时,线程池会补充一个新的线程。


  • newCachedThreadPool() 方法,创建一个可缓存的线程池。


  • 如果线程池的规模超过了处理需求,将自动回收空闲线程。
  • 当需求增加时,则可以自动添加新线程。线程池的规模不存在任何限制。


  • newSingleThreadExecutor() 方法,创建一个单线程的线程池。


  • 它创建单个工作线程来执行任务,如果这个线程异常结束,会创建一个新的来替代它。
  • 它的特点是,能确保依照任务在队列中的顺序来串行执行。


定时任务线程池


  • newScheduledThreadPool(int corePoolSize) 方法,创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似 Timer 。
  • newSingleThreadExecutor() 方法,创建了一个固定长度为 1 的线程池,而且以延迟或定时的方式来执行任务,类似 Timer 。


 线程池的关闭方式


ThreadPoolExecutor 提供了两个方法,用于线程池的关闭,分别是:


  • shutdown() 方法,不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务。
  • shutdownNow() 方法,立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务


26 总结


  由于java多线程并发涉及到的知识点太多了,这边不可能一一列全,不过我会在后续的更新中一一去补充完善,而且会涉及原理以及源码层次的解析。谢谢观看,有错误欢迎指出更改!!


各位看官还可以吗?喜欢的话,动动手指点个💗,点个关注呗!!谢谢支持!

目录
相关文章
|
28天前
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
128 0
|
13天前
|
安全
List并发线程安全问题
【10月更文挑战第21天】`List` 并发线程安全问题是多线程编程中一个非常重要的问题,需要我们认真对待和处理。只有通过不断地学习和实践,我们才能更好地掌握多线程编程的技巧和方法,提高程序的性能和稳定性。
122 59
|
2天前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
16 4
|
4天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
9天前
|
Java 数据库连接 数据库
如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面
本文介绍了如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面。通过合理配置初始连接数、最大连接数和空闲连接超时时间,确保系统性能和稳定性。文章还探讨了同步阻塞、异步回调和信号量等并发控制策略,并提供了异常处理的最佳实践。最后,给出了一个简单的连接池示例代码,并推荐使用成熟的连接池框架(如HikariCP、C3P0)以简化开发。
22 2
|
26天前
|
Java
【编程进阶知识】揭秘Java多线程:并发与顺序编程的奥秘
本文介绍了Java多线程编程的基础,通过对比顺序执行和并发执行的方式,展示了如何使用`run`方法和`start`方法来控制线程的执行模式。文章通过具体示例详细解析了两者的异同及应用场景,帮助读者更好地理解和运用多线程技术。
25 1
|
1月前
|
算法 Java 数据中心
探讨面试常见问题雪花算法、时钟回拨问题,java中优雅的实现方式
【10月更文挑战第2天】在大数据量系统中,分布式ID生成是一个关键问题。为了保证在分布式环境下生成的ID唯一、有序且高效,业界提出了多种解决方案,其中雪花算法(Snowflake Algorithm)是一种广泛应用的分布式ID生成算法。本文将详细介绍雪花算法的原理、实现及其处理时钟回拨问题的方法,并提供Java代码示例。
59 2
|
2月前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
2月前
|
Java API 容器
JAVA并发编程系列(10)Condition条件队列-并发协作者
本文通过一线大厂面试真题,模拟消费者-生产者的场景,通过简洁的代码演示,帮助读者快速理解并复用。文章还详细解释了Condition与Object.wait()、notify()的区别,并探讨了Condition的核心原理及其实现机制。
|
1月前
|
JSON 安全 前端开发
第二次面试总结 - 宏汉科技 - Java后端开发
本文是作者对宏汉科技Java后端开发岗位的第二次面试总结,面试结果不理想,主要原因是Java基础知识掌握不牢固,文章详细列出了面试中被问到的技术问题及答案,包括字符串相关函数、抽象类与接口的区别、Java创建线程池的方式、回调函数、函数式接口、反射以及Java中的集合等。
27 0