【面试必看】多线程

5 篇文章 0 订阅
  1. 多线程概述
    1.1. 多线程编程存在的问题与风险
  1. 线程安全(Thread safe)问题.
    多线程共享数据时,如果没有采取正确的并发访问控制措施,就可能会产生数据一致性问题,如读取脏数 据(过期的数据), 如丢失数据更新.
  2. 线程活性(thread liveness)问题.
    由于程序自身的缺陷或者由资 源稀缺性导致线程一直处于非 RUNNABLE 状态,这就是线程活性问题, 常见的活性故障有以下几种:
    (1) 死锁(Deadlock). 类似鹬蚌相争.
    (2) 锁死(Lockout), 类似于睡美人故事中王子挂了
    (3) 活锁(Livelock). 类似于小猫咬自己尾巴
    (4) 饥饿(Starvation).类似于健壮的雏鸟总是从母鸟嘴中抢到食物.
  3. 上下文切换(Context Switch).
    处理器从执行一个线程切换到执 行另外一个线程
    4)可靠性.
    可能会由一个线程导致 JVM 意外终止,其他的线程也 无法执行.
  1. 线程安全问题
    非线程安全主要是指多个线程对同一个对象的实例变量进行操作 时,会出现值被更改,值不同步的情况.
    线程安全问题表现为三个方面: 原子性,可见性和有序性 。
    2.1. 原子性
    含义:操作不可分割、操作顺序不变
    2.2. 可见性
    概念:其他线程能否立即读到共享变量的更新
    2.3. 有序性
    几个概念:
    源代码顺序:源码中指定的内存访问顺序.
    程序顺序:处理器上运行的目标代码所指定的内存访问顺序
    执行顺序:内存访问操作在处理器上的实际执行顺序
    感知顺序:给定处理器所感知到的该处理器及其他处理器的内存访问操作的顺序
    重排序:对内存访问有序操作的一种优化,可以在不影响单线程 程序正确的情况下提升程序的性能.但是,可能对多线程程序的正确性产生影响,即可能导致线程安全问题
    指令重排序 存储子系统重排序
    情形 源码顺序与程序顺序不一致,或者程序顺序与执行顺序不一致 其他处理器对两个内存访问操作的感知顺序与程序顺序不一致,这两个操作的顺序顺序看起来像是发生了变化,
    调整顺序 是 否

  2. 保障线程安全方法
    控制资源:锁、volatile、CAS
    增加资源:ThreadLocal
    3.1. 线程同步
    3.1.1. 锁
    锁的分类
    可见性<——锁的获得隐含刷新处理器缓存(将数据从内存读到缓存)的动作, 锁的释放隐含冲刷处理器缓存(将数据从缓存写到内存)的动作
    有序性、原子性
    3.1.1.1. Synchronized与lock区别
    内部锁synchronized 显示锁lock
    java内置关键字,在jvm层面 java类
    无法判断是否获取锁 可以判断是否获取到锁
    自动释放锁(执行完、异常) 手动释放
    可重入、不可中断、非公平 可重入、可判断、可公平(两者皆可)
    适合代码少量的同步问 适合大量代码的同步问题

3.1.1.2. Synchronized
同步代码块 锁对象是this
锁对象是静态变量
同步方法 同步实例方法:锁对象是this
同步静态方法:锁对象是当前类的运行时类对象
3.1.1.3. ReentrantLock可重入锁
 lockInterruptibly() 方法:如果当前线程未被中断则获得锁, 如果当前线程被中断则出现异常.
 tryLock(long time, TimeUnit unit) :在给定等待时长内锁没有被另外的线程持有,并且当前线程也没有被中断,则获得该锁。通过该方法可以实现锁对象的限时等待.
newCondition() :为了灵活性,lock将同步互斥控制和等待队列分离开来,互斥保证在某个时刻只有一个线程访问临界区(lock自己完成),等待队列负责保存被阻塞的线程(condition完成)。通过查看ReentrantLock的源代码发现,condition其实是等待队列的一个管理者,condition确保阻塞的对象按顺序被唤醒。
实现等待通知模式await()/signal()
 int getHoldCount() 返回当前线程调用 lock()方法的次数
int getQueueLength() 返回正等待获得锁的线程预估数
int getWaitQueueLength(Condition condition) 返回与 Condition 条件 相关的等待的线程预估数
boolean hasQueuedThread(Thread thread) 查询参数指定的线程是否 在等待获得锁
boolean hasQueuedThreads() 查询是否还有线程在等待获得该锁 boolean hasWaiters(Condition condition) 查询是否有线程正在等待 指定的 Condition 条件
boolean isFair() 判断是否为公平锁
boolean isHeldByCurrentThread() 判断当前线程是否持有该锁 boolean isLocked() 查询当前锁是否被线程持有;
 ReentrantReadWriteLock 读写锁
3.1.1.4. 锁的优化
1)程序员:
 减少锁持有时间
 减小锁的粒度
 使用读写分离锁代替独占锁
 锁分离
 粗锁化
2)JVM对锁的优化
 锁偏向(类似局部性原理)
 轻量级锁(自旋几次)

3.1.2. 轻量级同步机制Volatile关键字
volatile 作用:实现可见性 ,非原子性
volatile可见性原理:
当对volatile变量执行写操作后,JMM会把工作内存中的最新变量值强制刷新到主内存,写操作会导致其他线程中的缓存无效,这样其他线程使用缓存时,发现本地工作内存中此变量无效,便从主内存中获取,这样获取到的变量便是最新的值,实现了线程的可见性。
3.1.3. CAS
原理:在把数据更新到主内存时,再次读取主内存变量的值,如 果现在变量的值与期望的值(操作起始时读取的值)一样就更新

ABA问题解决办法:引入修订号(时间戳)
3.1.4. ThreadLock
为每个线程绑定自己的值
3.2. Java 运行时存储空间(内存区域)
Java 内存区域和内存模型是不一样的东西,内存区域是指 Jvm 运行时将数据分区域存储,强调对内存空间的划分。
而内存模型(Java Memory Model,简称 JMM )是定义了线程和主内存之间的抽象关系,即 JMM 定义了 JVM 在计算机内存(RAM)中的工作方式。

https://www.cnblogs.com/czwbig/p/11127124.html
堆和方法区(实例变量&静态变量)是线程共享的,可能存在线程安全问题
栈(局部变量)是线程独有的
3.3. 使用无状态对象
状态:对象所包含的数据
状态变量:实例变量与静态变量
无状态对象:一个类的同一个实例被多个线程共享并不会使这些线程存储共享的状态,即不包含任何实例变量和静态变量的对象
3.4. 使用不可变对象
3.4.1. 概念
不可变对象是指一经创建它的状态就保持不变的对象,不可变对象具有固有的线程安全性. 当不可变对象现实实体的状态发生变化 时,系统会创建一个新的不可变对象,就如 String 字符串对象.
3.4.2. 一个不可变对象需要满足以下条件:

  1. 类本身使用 final 修饰,防止通过创建子类来改变它的定义
  2. 所有的字段都是 final 修饰的,final 字段在创建对象时必须显示初始化,不能被修改
  3. 如果字段引用了其他状态可变的对象(集合,数组),则这些字段 必须是 private 私有的
    3.4.3. 不可变对象主要的应用场景:
  4. 被建模对象的状态变化不频繁
  5. 同时对一组相关数据进行写操作,可以应用不可变对象,既可以保障原子性也可以避免锁的使用
  6. 使用不可变对象作为安全可靠的 Map 键, HashMap 键值对的存 储位置与键的 hashCode()有关,如果键的内部状态发生了变化会导致 键的哈希码不同,可能会影响键值对的存储位置. 如果 HashMap 的键 是一个不可变对象,则 hashCode()方法的返回值恒定,存储位置是固定的.
    3.5. 使用线程特有对象
    概念:各个线程创建各自的实例,一个实例只能被一个线程访问的对象
    实现线程安全原理:不共享非线程安全的对象,对于非线程安全的对象, 每个线程都创建一个该对象的实例
    3.6. 装饰器模式
    基本思想:为非线程安全的对象创建一个相应的线程安全的外包装对象,外包装对象内部通常会借助锁,以线程安全的方式调用相应的非线程安全对象的方法.
  1. 线程通信
    4.1. 等待/通知机制wait()/notify()
  1. wait()方法只能在同步代码块中由锁对象调用 2) 调用 wait()方法,当前线程会释放锁
    在同步代码块中调用 notify()方法后,并不会立 即释放锁对象,需要等当前同步代码块执行完后才会释放锁对象.
    interrupt()方法会中断 wait(); wait(long); notifyAll(); 通知过早;条件发生变化
  1. 线程管理
    5.1. 线程组(可忽略)
    activeCount() 返回当前线程组及子线程组中活动线程的数量(近似值)
    activeGroupCount() 返回当前线程组及子线程组中活动线程组的数量 (近似值)
    enumerate(Thread[] list) 把当前线程组和子线程组中所有的线程 复制到参数数组中
    enumerate(Thread[] list, boolean recursive) , 如果第二个参数设置 为 false,则只复制当前线程组中所有的线程,不复制子线程组中的线程
    enumerate(ThreadGroup[] list) 把当前线程组和子线程组中所有的线程组复制到参数数组中
    enumerate(ThreadGroup[] list, boolean recursive) 第二个参数设置false,则只复制当前线程组的子线程组
    getMaxPriority() 返回线程组的最大优先级,默认是 10
    getName() 返回线程组的名称
    getParent() 返回父线程组
    interrupt() 中断线程组中所有的线程
    isDaemon() 判断当前线程组是否为守护线程组
    list() 将当前线程组中的活动线程打印出来
    parentOf(ThreadGroup g) 判断当前线程组是否为参数线程组的父线 程组
    setDaemon(boolean daemon) 设置线程组为守护线程组
    5.2. 捕获线程的执行异常
    getDefaultUncaughtExceptionHandler() 获 得 全 局 的 ( 默 认 的) UncaughtExceptionHandler
    getUncaughtExceptionHandler() 获 得 当 前 线 程 的 UncaughtExceptionHandler
    setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHan dler eh) 设置全局的 UncaughtExceptionHandler
    setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) 设置当前线程的 UncaughtExceptionHandler
    5.3. 注入 Hook (钩子)线程
    5.4. 线程池

各个参数含义:
corePoolSize:指定线程池中核心线程的数量
maxinumPoolSize:指定线程池中最大线程数量
keepAliveTime:当线程池线程的数量超过
corePoolSize:多余的空闲线程的存活时长,即空闲线程在多长时长内销毁
unit:keepAliveTime 时长单位
workQueue:任务队列,把任务提交到该任务队列中等待执行
threadFactory:线程工厂,用于创建线程Thread newThread(Runnable r)
handler:拒绝策略,当任务太多来不及处理时,如何拒绝
5.4.1. 任务队列
workQueue 工作队列是指提交未执行的任务队列,它是 BlockingQueue 接口的对象,仅用于存储 Runnable 任务.根据队列功 能分类,在 ThreadPoolExecutor 构造方法中可以使用以下几种阻塞 队列:

  1. 直接提交队列
    由 SynchronousQueue 对象提供,该队列没有容量,提交给线程池的任务不会被真实的保存,总是将新的任务提交给线程执行,如果没有空闲线程,则尝试创建新的线程,如果线程数量已经达到maxinumPoolSize规定的最大值则执行拒绝策略.

  2. 有界任务队列
    由 ArrayBlockingQueue 实现,在创建 ArrayBlockingQueue 对象时,可以指定一个容量. 当有任务需要执行时,如果线程池中线程数小于 corePoolSize 核心线程数则创建新 的线程;如果大于 corePoolSize 核心线程数则加入等待队列.如果队 列已满则无法加入,在线程数小于 maxinumPoolSize 指定的最大线 程数前提下会创建新的线程来执行,如果线程数大于 maxinumPoolSize 最大线程数则执行拒绝策略

  3. 无界任务队列
    由 LinkedBlockingQueue 对象实现,与有界队 列相比,除非系统资源耗尽,否则无界队列不存在任务入队失败的情况. 当有新的任务时,在系统线程数小于 corePoolSize 核心线程 数则创建新的线程来执行任务;当线程池中线程数量大于corePoolSize 核心线程数则把任务加入阻塞队列

  4. 优先任务队列
    通过 PriorityBlockingQueue 实现的,是带有任务优先级的特殊的无界队列,不管是 ArrayBlockingQueue 队列还是 LinkedBlockingQueue 队列都是按照先进先出算法处理任务的.在 PriorityBlockingQueue 队列中可以根据任务优先级顺序先后执行.
    5.4.2. 拒绝策略
    JDK提供的拒绝策略:
    AbortPolicy 策略,会抛出异常 (默认)
    CallerRunsPolicy 策略,只要线程池没关闭,会在调用者线程中运行 当前被丢弃的任务
    DiscardOldestPolicy 将任务队列中最老的任务丢弃,尝试再次提交 新任务
    DiscardPolicy 直接丢弃这个无法处理的任务
    自定义拒绝策略
    5.4.3. 监控线程池
    int getActiveCount() 获得线程池中当前活动线程的数量
    long getCompletedTaskCount() 返回线程池完成任务的数量
    int getCorePoolSize() 线程池中核心线程的数量
    int getLargestPoolSize() 返回线程池曾经达到的线程的最大数
    int getMaximumPoolSize() 返回线程池的最大容量
    int getPoolSize() 当前线程池的大小
    BlockingQueue getQueue() 返回阻塞队列
    long getTaskCount() 返回线程池收到的任务总数
    5.4.4. 扩展线程池
    protected void afterExecute(Runnable r, Throwable t)
    protected void beforeExecute(Thread t, Runnable r)
    5.4.5. 线程池中的异常处理
    在使用 ThreadPoolExecutor 进行 submit 提交任务时,有的任务抛出 了异常,但是线程池并没有进行提示,即线程池把任务中的异常给吃掉 了,可以把 submit 提交改为 execute 执行,也可以对 ThreadPoolExecutor 线程池进行扩展.对提交的任务进行包装.
    5.4.6. ForkJoinPool 线程池

分而治之

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Nydia~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值