恶补并发编程基础知识

[TOC]

现代计算机的 CPU 大多是多核心,比如我们使用到的 Android 手机很多已经是 8 核处理器,核心数和线程数多为 1:1 的关系,英特尔处理器的超线程技术提高了线程数,成 1:2 的关系

多线程

Java 天生就是支持多线程的,并且提供了两种方式来实现多线程

  1. 继承 Thread
  2. 实现 Runnable 接口

网上还提到有其他创建线程方式,其实最终只有两种,为什么呢?在 Jdk Thread 源码里就说明了
Jdk Thread.class,比如 Callable 的方式实现多线程,实际上就是 Runnable,因为 Callable 的方式是使用 FutureTask ,而 FutureTask 最终实现了Runnable 接口

锁分类
美团出品,锁
根据锁的 api 属性可有多种分类

  1. 内置锁 - sync
  2. 显示锁 - Lock

根据锁的分配方式分类

  1. 公平锁 - 谁先申请谁先使用,类似队列的先进先出
  2. 非公平锁-竞争使用

Synchornized 原理

ObjectMonitor 对象监视器,使用了 sync 关键字以后,编译器阶段会转换为相应的语句,里面就有监视器

作用于对象,也就是对象实例上

作用范围

分类 被锁的对象 Code 说明
方法 实例对象 public synchronized void fun() 1. 锁住的是该实例对象 2. 同一个实例对象在不同线程调用会同步 3. 不同实例对象在不同线程调用不会调用
方法 静态方法 public static synchronized void fun() 1. 锁住的是该类的 Class 对象 2. 该类的实例对象在不同的线程调用都是同步
代码块 实例对象 synchronized (this) 1. 锁住的是实例对象 2. 该类的实例对象在不同的线程调用都是同步 3. 该类的不同实例对象在不同线程是不同步的
代码块 类对象 synchronized (xx.class) 1. 锁住的是该类的 Class 对象 2. 该类的实例对象在不同的线程调用都是同步
代码块 任意对象 Object obj; synchronized(obj) 锁住的是 obj 对象实例,具体根据 obj 的类型,如果是实例对象,则根据实例的规则,如果是类对象,则是类对象的规则

// 伪代码

// monitorenter 

// 记录对应的线程 Id ,其他线程不许操作该对象的该方法

// monitorexit 


sync 原理

锁升级

偏向锁废弃原因

  • 偏向锁 没有线程争夺资源,当执行同步代码时,会讲会将该对象头中偏向锁指向当前线程,避免 cas
  • 轻量级锁

线程状态

线程状态.png
  • 初始
  • 就绪
  • 运行中
  • 阻塞
  • 等待
  • 等待超时
  • 终止

进入阻塞状态的唯一条件是通过 synchronized() 进入,阻塞是被动进入,等待则是主动进入,比如调用 sleep() 进入等待超时,调用 lock() 进入等待、等待超时状态

为什么会有就绪和运行中这两种状态切换呢?因为线程分配的时间片资源使用完后,就会进入xxx 状态,等待下一次时间片的分配

死锁

  • 互斥
  • 条件等待
  • 不剥夺

打破死锁:使用 trylock()

ThreadLocal

线程本地变量,用来储存当前线程的副本

  • 自己怎么实现
  • Jdk 是怎么实现的

Thread 内部自己存储了副本,保存在静态内部类 ThreadLocalMap 中的 Entry 数组中

CAS

现代计算机提供了一条指令,简称 CAS

Compare And Swap,取出该值跟期望的旧值做比较,看是否发生变化,如果仍然是旧值,则将该值更新成新值,如果不是旧值则自旋

系统中有相关的 CAS 指令,jdk 基于 CAS 实现很多原子类

原子操作,指不可再分的操作,执行的结果是要么都做了,要么都没做,比如 sync 操作,内部的代码段要么获得锁执行,要么阻塞无法执行,但 sync 并不是 CAS

  • 应用
    Java API 中的原子类 AutomaticInteger 等,该类中 increaseAndGet() 方法,内部是由 Unsafe 类调用 native 方法,HotSpot 对 x86 平台的实现,是由汇编指令 lock cmpxchg 指令实现

  • ABA 问题

假设某个值为 A 的变量,中间变成了 B,然后又重新变成了 A,此时执行 CAS 指令时,A 虽然经过了变化,但仍为期待的旧值,就对它更新成新值

怎么解决 ABA 问题呢?

设置一个版本戳,比如 A 发生一次改变,标志位 +1,jdk 中的 AtomicMarkableReference(记录是否发生过改变) 和 AtomicStampedReference (记录了发生改变的次数)

  • 开销问题

大量的自旋消耗 cpu

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

怎么解决呢?把几个要同时更新值的变量封装成一个对象,此时更新该对象就是一个原子操作了

阻塞队列

Java 阻塞队列 --BlockingQueue

解决生产者与消费者模型中两方处理速率不同步的问题

image.png

线程池

由于线程的不仅在运行时需要耗时,在创建、销毁线程的过程中也是需要时间的,如果在我们的程序中频繁地创建、销毁线程,会造成很多不必要的消耗,那么就需要使用到容器,使得线程能够复用,它就是线程池

Java线程池实现原理及其在美团业务中的实践

  • 线程池的创建
  1. 核心线程数
  2. 最大线程数
  3. 阻塞队列
  4. 拒绝策略
  5. 线程存活时间

假设创建线程池的核心线程数 3,最大线程数为15,此时我们在执行三个任务,核心线程刚好跑满,继续提交 1 个任务,不会马上启动新线程,而是放到阻塞队列中,直到阻塞队列填满后才会创建新线程,如果不断地创建线程,直到达到最大线程数时,触发拒绝策略

  • 拒绝策略
  1. DiscardOldestPolicy 抛弃最老的任务
  2. AbortPolicy 直接抛出异常,默认执行该策略
  3. CallerRunsPolicy 调用者执行该任务
  4. DiscardPolicy 抛弃最新提交的任务
  • 任务提交
  1. Submit 有返回值
  2. Excute 入参为 runnale,有无返回值
  • 销毁线程池
  1. shutdown
  2. shutdownNow 立即发送中断指令

当调用线程池中断操作时,会对正在运行的线程设置中断指令,也就是执行 interrupt() 方法,最终线程是否停止,取决于线程内部是否有对中断信号进行处理

AQS

全称为 AbstractQueuedSynchronier ,抽象的队列式同步器,是一个由 Jdk 提供的底层同步器,,显示锁是基于它来实现的,具体内容可以参考这篇博客( 从 ReentrantLock 的实现看 AQS 的原理及应用 - 美团技术团队 )

  • 使用 AQS 有什么作用呢?
  • AQS 内部是怎样实现的呢?

  • CLH

  • 基于 AQS 实现独占锁

  • 基于 AQS 实现可重入锁

Java 内存模型 [JMM]

现在计算机处理器由于 cpu 与内存的速度差距太大,为了提高运算效率,引入了多级缓存的概念(L1/L2/L3等)。每次 CPU 运算前将数据从内存复制到缓存中,运算时直接从缓存中获取,运算结束后又将结果写回内存

Java 虚拟机定义了一套 Java 内存模型(JMM),分为线程工作内存和主内存,类似缓存和内存的对应关系。比如多个线程执行 count = count+1 操作时(count 为 0),首先线程会从主内存中读取 count 的值到工作内存

  • volatile 关键字是什么

能保证操作的可见性,但不能保证操作的原子性,是什么意思呢?比如上面提到多线程自加,当工作线程中 +1 后,会马上将主内存对应的值也进行 +1

举个栗子:当一个线程从主内存读取时 count == 1,它工作内存中 count 的值也为 1,执行 count + 1 后,由于 count 使用 volatile 修饰,主内存的值也会立刻更新,如果此时该线程的时间片用完了(上下文切换),但 count 赋值的操作还并未完成,当线程重新恢复执行时,从主内存读取 count 的值此时已经为 2 了

当只有一个线程写,多个线程读的时候,volatile 是够用的

  • volatile 的实现原理

使用 volatile 关键字的共享变量,在写操作前会使用 cpu 提供的 lock 前缀指令

  1. 保证了工作内存中的值实时刷新到主内存中
  2. 强制使得其他工作内存中的值失效,重新从主内存中获取最新的值
 
 volatile int count = 0;

  • 使用双检锁实现单例模式,为什么使用了 volatile 关键字 ,仍然需要加锁呢?

在双检锁的实现中,对象实例使用 volatile 修饰,但由于 volatile 关键字的只能保证可见性,而创建对象,并且给 INSTANCE 实例的操作在 JVM 中 并不是原子操作,所以需要用到 synchornied()来保证原子性。

这么说的话,那么单纯使用锁的话就好了,那为什么又要用到 volatile 呢?

  • volatile 和 synchornized 有什么区别呢?

volatile 和 synchornized 都是解决多线程数据同步问题的,但是它俩的作用范围不一样, volatile 是轻量级的(非锁),只能保证及时将工作内存中的值刷新到主内存中。而 synchornized 是重量级锁,既能保证可见性又能保证原子性

大厂面试题

  • sychronied修饰普通方法和静态方法的区别?什么是可见性?
  • 锁分哪几类?
  • CAS无锁编程的原理。
  • ReentrantLock 的实现原理。
  • AQS 原理(小米、京东)
  • Synchronized 的原理以及与 ReentrantLock 的区别。(360)
  • Synchronized 做了哪些优化(京东)
  • Synchronized static 与非 static 锁的区别和范围(小米)
  • volatile 能否保证线程安全?在 DCL 上的作用是什么?
  • volatile 和synchronize 有什么区别?(B站小米京东)
  • 什么是守护线程?你是如何退出一个线程的?
  • sleep、wait、yield 的区别,wait 的线程如何唤醒它?(京东、头条)
  • sleep 是可中断的么?(小米)
  • 线程生命周期。
  • ThreadLocal 是什么?
  • 线程池基本原理
  • 三个线程 T1、T2、T3, 怎样确保按顺序执行
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容