多线程、并发问题、锁编程基本概念

注:本文主要是总结下多线程、并发编程、锁编程相关概念,内容相对简略。

注:如有侵权,请联系删除。

1、什么是多线程

线程是程序执行的最小单位,多个线程并发或并行执行就是多线程。多线程下需要保证执行结果的最终正确性。

  • 并发:两个及两个以上的作业在同一 时间段 内执行。

  • 并行:两个及两个以上的作业在同一 时刻 执行。

2、为什么使用多线程

  • 充分发挥多核性能优势

  • 单一线程同步执行IO操作时,存在长时间的阻塞,无法充分使用CPU

3、多线程下可能导致什么问题

根本问题:最终结果与单线程执行结果不一致。

常见衍生问题:

  • 死锁

  • 内存泄漏

  • 线程不安全

  • ...

4、线程模型:用户线程和内核线程之间的关联方式

多对一、一对一、多对多

three-types-of-thread-models.png

5、线程生命周期和状态:

  • 初始状态

  • 运行状态

  • 阻塞状态

  • 等待状态

  • 超时等待状态

  • 结束状态

threadStatus.png

6、线程间通信

  • 管道流

  • 等待/通知机制

  • 共享内存

  • threadLocal

7、线程上下文切换是什么,什么场景下发生?

线程上下文切换是指在特定场景下,在CPU上执行的线程发生变化,需要记录上个线程的执行状态, 以及恢复新线程之前的执行状态。

  • 线程主动让出cpu

  • 线程CPU时间片用尽,系统调度防止其他线程饿死。

  • 系统中断

  • 线程任务执行完成

8、死锁四个必要条件:

1、互斥条件

2、不可剥夺条件

3、请求与保持条件

4、循环等待

9、JMM

指令重排序:JIT编译优化 + CPU指令重排序

happenBefore原则

JMM.png

线程安全三大特性:原子性、可见性、有序性

10、CAS(Compare-And-Swap, 比较并交换)

在 Java 中,实现 CAS(Compare-And-Swap, 比较并交换)操作的一个关键类是Unsafe

CAS 算法存在以下问题
  • ABA 问题是 CAS 算法最常见的问题。

    ABA 问题的解决思路是在变量前面追加上版本号或者时间戳。JDK 1.5 以后的 AtomicStampedReference 类就是用来解决 ABA 问题的,其中的 compareAndSet() 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

  • 循环时间长开销大

    依照使用场景来选择是否使用CAS,写多读少,用锁,读多写少, 用CAS

  • 只能保证一个共享变量的原子操作

    CAS 操作仅能对单个共享变量有效。当需要操作多个共享变量时,CAS 就显得无能为力。不过,从 JDK 1.5 开始,Java 提供了AtomicReference类,这使得我们能够保证引用对象之间的原子性。通过将多个变量封装在一个对象中,我们可以使用AtomicReference来执行 CAS 操作。

11、synchronized 关键字

在 Java 6 之后, synchronized 引入了大量的优化如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销,实现核心原理是对象monitor的获取,对象头中有锁标志位。

12、ReentrantLock

ReentrantLock 比 synchronized 增加了一些高级功能
  • 公平与非公平实现

  • 可中断

  • 可选择通知

13、ThreadLocal

ThreadLocal原理

最终的变量是放在了当前线程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上,ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值。 ThrealLocal 类中可以通过Thread.currentThread()获取到当前线程对象后,直接通过getMap(Thread t)可以访问到该线程的ThreadLocalMap对象。

每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为 key ,Object 对象为 value 的键值对。

ThreadLocal 内存泄露问题

ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用。所以,如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。

这样一来,ThreadLocalMap 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话,value 永远无法被 GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap 实现中已经考虑了这种情况,在调用 set()get()remove() 方法的时候,会清理掉 key 为 null 的记录。使用完 ThreadLocal方法后最好手动调用remove()方法

14、线程池

池化技术

优点:

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

Java创建线程池参数:核心线程数、最大线程数、空线程存活时间、时间单位、任务队列、丢弃策略、线程工厂。

线程池的拒绝策略

如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任务时,ThreadPoolExecutor 定义一些策略:

  • ThreadPoolExecutor.AbortPolicy:抛出 RejectedExecutionException来拒绝新任务的处理。

  • ThreadPoolExecutor.CallerRunsPolicy:调用执行自己的线程运行任务,也就是直接在调用execute方法的线程中运行(run)被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。如果你的应用程序可以承受此延迟并且你要求任何一个任务请求都要被执行的话,你可以选择这个策略。

  • ThreadPoolExecutor.DiscardPolicy:不处理新任务,直接丢弃掉。

  • ThreadPoolExecutor.DiscardOldestPolicy:此策略将丢弃最早的未处理的任务请求。

拓展:

如果服务器资源以达到可利用的极限,这就意味我们要在设计策略上改变线程池的调度了,我们都知道,导致主线程卡死的本质就是因为我们不希望任何一个任务被丢弃。换个思路,有没有办法既能保证任务不被丢弃且在服务器有余力时及时处理呢?

这里提供的一种任务持久化的思路,这里所谓的任务持久化,包括但不限于:

  1. 设计一张任务表将任务存储到 MySQL 数据库中。

  2. Redis 缓存任务。

  3. 将任务提交到消息队列中。

这里以方案一为例,简单介绍一下实现逻辑:

  1. 实现RejectedExecutionHandler接口自定义拒绝策略,自定义拒绝策略负责将线程池暂时无法处理(此时阻塞队列已满)的任务入库(保存到 MySQL 中)。注意:线程池暂时无法处理的任务会先被放在阻塞队列中,阻塞队列满了才会触发拒绝策略。

  2. 继承BlockingQueue实现一个混合式阻塞队列,该队列包含 JDK 自带的ArrayBlockingQueue。另外,该混合式阻塞队列需要修改取任务处理的逻辑,也就是重写take()方法,取任务时优先从数据库中读取最早的任务,数据库中无任务时再从 ArrayBlockingQueue中去取任务。

常用阻塞队列
  • LinkedBlockingQueue

  • ArrayBlockingQueue

  • SynchronousQueue

  • DelayedWorkedQueue

线程池中线程异常是复用还是销毁?
  • execute 销毁

  • submit 复用

核心线程数参数确定

CPU密集型 (N+1)

IO密集型(2N)

如果有资源的话 ,结合实际情况具体压测下。

动态线程池

https://mp.weixin.qq.com/s/9HLuPcoWmTqAeFKa1kj-_A

15、CompletableFuture

引用:CompletableFuture从入门、踩坑、迷茫、到精通(全网看这一篇够了)_completablefuture.get()坑-CSDN博客

16、AQS

引用:AQS 详解 | JavaGuide

17、为什么 wait() 方法不定义在 Thread 中?

wait() 是让获得对象锁的线程实现等待,会自动释放当前线程占有的对象锁。每个对象(Object)都拥有对象锁,既然要释放当前线程占有的对象锁并让其进入 WAITING 状态,自然是要操作对应的对象(Object)而非当前的线程(Thread)。

类似的问题:为什么 sleep() 方法定义在 Thread 中?

因为 sleep() 是让当前线程暂停执行,不涉及到对象类,也不需要获得对象锁。

18、如何理解线程安全和不安全?

线程安全和不安全是在多线程环境下对于同一份数据的访问是否能够保证其正确性和一致性的描述。

  • 线程安全指的是在多线程环境下,对于同一份数据,不管有多少个线程同时访问,都能保证这份数据的正确性和一致性。

  • 线程不安全则表示在多线程环境下,对于同一份数据,多个线程同时访问时可能会导致数据混乱、错误或者丢失。

19、如何预防死锁? 破坏死锁的产生的必要条件即可:

  1. 破坏请求与保持条件:一次性申请所有的资源。

  2. 破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。

  3. 破坏循环等待条件:靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

这个让我想起了经典的哲学家进餐问题:

如上有三种解法:

1、设置临界区,一个人一次性申请左右两个叉子。

2、设置信号量,最多 4人同时持有叉子。

3、奇数位申请顺序,先左后右;偶数位先右后左。

关于多线程,多考量下业务场景

引用目录

AQS 详解 | JavaGuide

CompletableFuture从入门、踩坑、迷茫、到精通(全网看这一篇够了)_completablefuture.get()坑-CSDN博客

https://mp.weixin.qq.com/s/9HLuPcoWmTqAeFKa1kj-_A

Java并发常见面试题总结(下) | JavaGuide

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,607评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,239评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,960评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,750评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,764评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,604评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,347评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,253评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,702评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,893评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,015评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,734评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,352评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,934评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,052评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,216评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,969评论 2 355

推荐阅读更多精彩内容

  • 前言目前CPU的运算速度已经达到了百亿次每秒,所以为了提高生产率和高效地完成任务,基本上都采用多线程和并发的运作方...
    Lemonrel阅读 556评论 0 0
  • 参考链接:CS-Notes/notes/Java 并发.md [https://github.com/CyC201...
    senzx阅读 221评论 0 0
  • 1.并行和并发有什么区别 并行:同一时刻执行多条指令,物理上的同时发生。 并发:同一时刻只能执行一条指令,但是多条...
    下一杯清淡若水_78db阅读 1,086评论 0 0
  • 1. 什么是线程和进程? 1.1 进程 进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统...
    ficca阅读 128评论 0 0
  • 个人博客http://www.milovetingting.cn Java多线程(四) 前言 本文为学习Java相...
    milovetingting阅读 413评论 0 1