https://www.bilibili.com/video/BV16J411h7Rd 小林code八股

多线程

内存模型JMM - 介绍

  • 解决多线程并发的一套规则,规定了在多线程环境下,线程怎么访问共享变量才能不出错,核心处理是可见性、原子性、有序性这三个问题。核心思路:规定变量必须从主内存加载到工作内存才能进行操作,改完再写回主内存。

  • 可见性:(e.g. 遇到线程读取变量时并未更新的情况,因为CPU有缓存,线程在工作时,会把主内存的变量读到自己的工作内存中,但是改完之后还没有即时更新值。解决办法:volatile关键字:改完变量后会立刻刷回主内存,同时使其他线程的缓存失效)

  • 原子性synchronizedorLock锁

  • 有序性

多线程是什么?需要注意的点

  • 多线程主要是在一个Java程序中运行多个线程,这些线程共享程序的内存空间,但有各自的栈和程序计数器。能同时执行不同的任务,提高了程序的效率

  • 需要注意的点:

    • 线程安全问题:

    • 线程间的通信:wait()notify()等等

    • 线程的创建和销毁成本(线程池)

Java中的线程和OS中的线程差异?

  • Java中创建线程的方式:pthread_create,所以本质上Java程序创建的线程和OS是一致的,都是1对1的线程模型

使用多线程要注意什么

  • 原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,在Java中使用了atomic包和关键字synchronized

  • 可见性:一个线程对主内存的修改可以即使被其他线程看到

  • 有序性:happens-before原则来确保有序性

保证数据一致性的方案

  • 事务管理:通过数据库的ACID的属性来保证数据的一致性

  • 锁机制:实现对共享资源的互斥访问。可以使用synchronized或者`ReentrantLock

线程的创建、启动、停止方式

  • 线程创建

    • 继承Thread
    • 实现Runnable接口
    • 实现Callable接口与FutrureTask
    • 使用线程池 Executor框架
  • 线程启动

    • Thread.start()
  • 线程停止

    • 异常法停止:interrupt()
    • 沉睡中停止:sleep() -> interrupt()
    • 暴力停止:`stop()
    • return停止

调用interrupt让线程抛出异常的原理

  • 一个boolean表示中断状态

    • 低级别中断可用方法 -> InterruptedException异常
    • 否则Thread.interrupt()仅设置线程的中断状态,轮询中断判断是否要定值执行任务

Java的线程状态有哪些

线程状态 说明
NEW 线程创建,还未调用start()
RUNNABLE 就绪状态start()+正在运行
BLOCKED 阻塞状态,等待监视器锁
WAITING 等待状态,正在等另一线程执行特定给的操作(notify等)
TIME_WAITING 等待状态:特定的等待时间
TERMINATED 终止状态,线程完成了执行

sleepwait的区别

特性 sleep() wait()
所属类 Thread类(静态方法) Object类(实例方法)
锁释放 no yes
使用前提 任意位置调用 同步块或同步方法内
唤醒机制 超时自动恢复 需要notify()/notifyAll()或超时
设计用途 暂停线程执行,不涉及锁写作 线程间协调,释放锁让其他线程工作

sleep会释放cpu吗

  • 会释放CPU,但不会释放持有的锁

  • 线程在调用sleep()后,会主动让出CPU时间片,进入TIMED_WAITING状态,此时会触发OS的进程调度,将CPU分配给其他处于就绪状态的线程

  • 不会释放持有的锁,因此,如果有其他线程试图获取同一把锁,他们让仍会被阻塞,直到原线程退出同步代码块

blockedwaitting的区别

  • 触发条件

    • BLOCKED状态通常是因为试图获取一个对象的锁,但是该锁已经被另一个线程持有。

    • WAITING状态通常是因为它正在等待另一个线程执行某些操作,这个时候线程将不会消耗CPU资源,不会参与锁的竞争

  • 唤醒机制

    • 阻塞状态的线程在等待锁的时候,一旦锁被释放,线程将有机会重新尝试获取锁。如果成功,就会BLOCKED -> RUNNABLE

    • 线程在WAITING状态时采用的是显式唤醒,如果线程调用了wait()方法,那么它必须等待另一个线程调用同一对象的notify()或者notifyAll()唤醒才行

线程如何从wait状态 -> running状态

  • WAITING -> RUNING的核心机制是通过外部事件的触发或资源可用性变化,比如等待的线程被其他线程对象唤醒,如notify()或者notifyAll()

notifynotifyAll区别

  • notify: 唤醒某一个线程,其他线程都处于WAITING的等待唤醒的状态,如果被唤醒的线程结束时没调用notify,其他线程就没人去唤醒,只能等待超时,或者被中断

  • notifyAll():所有的线程退出waiting状态,开始竞争锁

notify选择哪个线程

  • notify()在源码注释中说到notify选择唤醒的线程是任意的,依赖于具体的jvm
  • JVM有很多的实现,比较流行的是hotspot,主要是按照“先进先出”的顺序唤醒

不同线程之间是如何通信的

  • 共享变量:为了实现多线程的可见性,保证线程的安全,所以通常需要使用synchronized关键字或volatile关键字

  • 线程间协作wait(),notify(),notifyAll()方法用于线程间的协作

    • LockCondition接口提供了更加灵活的线程间的通信方式。对应的是:await(),signal(),signalall()方法

线程间的通信方式有哪些

  • 最基础的线程通信方式,基于对象的监视器(锁)机制:

    • wait(),notify(),notify()方法
  • LockCondition接口

    • Lock优于synchronized,Condition接口则配合
    • 更好的await(),signal(),signalall()方法
  • volatile:保证变量的可见性,他会保证对该变量的写操作立即刷新到主内存中,而读内存会从主内存读取最新的值

  • CountDownLatch是一个同步辅助类,它允许一个或多个线程等待其他线程完成操作

  • CyclicBarrier是一个同步辅助类,它允许一组线程相互等待,直到所有线程都达到某个公共屏障点

  • Semaphore是一个计数信号量,它可以控制同时访问特定资源的线程数量

如何停止一个线程

  • 共享标志位主动终止:定义一个可见的状态变量,由主线程控制其值

  • 中断机制:Thread.interrupt()

  • return取消任务:使用线程池提交任务,并通过Future.cancel()停止线程,依赖中断机制

  • 处理不可中断的阻塞操作。

Go的协程和Java的线程有什么区别

  • 调度模型不一样:

    • Java线程是OS级别的线程,也就是内核线程,他的创建、销毁、调度都是由操作系统内核来管理的
    • Go的协程则是用户态的轻量级线程,它是由Go运行时自己调度的,不需要经过系统内核
  • 消耗资源差别明显:

    • Java创建一个线程的开销是比较大的,默认下一个线程的栈空间大概是1MB左右,而且线程的创建和销毁都需要系统调用,开销不笑
    • 而协程就轻量多了,一个协程初始栈空间只有2KB,而且栈空间是可以伸缩的
  • 调度方式不一样:

    • Java是抢占式的,由OS的调度器决定说明时候切换线程,线程切换需要保存和恢复上下文,还涉及到用户态和内核态的切换,这个开销相对比较大
    • Go的切换成本比较低
  • 使用方式更简单

  • 通信机制差异很大

  • 高并发场景下go的表现会更好


并发安全

JUC包下常用的类有哪些

  • 线程池

    • ThreadPoolExecutor用于创建和管理线程池
    • Executors:线程池工厂类,提供了一系列静态方法来创建不同类型的线程池
  • 并发集合类:

    • ConcurrentHashMap:线程安全的哈希映射表,用于在多线程的环境下高效的存储和访问键值对
    • CopyOnWriteArrayList:线程安全的列表,在对列表进行修改操作时,会创建一个新的底层数组,将修改操作应用到新数组上,而读操作仍然可以在旧数组上进行
  • 同步工具类:

    • ConutDownLatch:通过一个计数器来实现,允许一个或多个线程完成操作后再继续执行
    • CycliBarrier: 让一组线程相互等待
    • Semaphore:信号量,用于控制同时访问某个资源的线程数量
  • 原子类:

    • AtomicInteger:原子整数类,提供了对整数类型的原子操作
    • AtomicReference: 原子引用类,用于对对象引用进行原子操作

怎么保证多线程的安全

  • synchronized
  • volatile
  • Lock接口和ReentrantLock
  • 原子类
  • 线程局部变量:ThreadLocal
  • 并发集合:ConcurrentHashMap,ConcurrentLinkedQueue
  • JUC工具类:Semaphore,CyclicBarrier

Java中常用的锁和使用的场景

  • 内置锁synchronized
  • ReentrantLock
  • 读写锁ReadWriteLock
  • 乐观锁和悲观锁
  • 自旋锁

实践开发时如何使用锁的

  • synchronized
  • Lock接口:如ReentrantLock
  • 读写锁ReadWriteLock

Java并发工具有哪些

  • ConutDownLatch:通过一个计数器来实现,允许一个或多个线程完成操作后再继续执行
  • CycliBarrier: 让一组线程相互等待
  • Semaphore:信号量,用于控制同时访问某个资源的线程数量
  • FutureCallable
  • ConcurrentHashMap:线程安全的哈希映射表,用于在多线程的环境下高效的存储和访问键值对
  • CopyOnWriteArrayList:线程安全的列表,在对列表进行修改操作时,会创建一个新的底层数组,将修改操作应用到新数组上,而读操作仍然可以在旧数组上进行

CountDownLatch有什么用

  • 作用:用于让一个或多个线程等待其他线程完成操作后再继续执行

  • 一个计数器实现线程间的协调:

    • 初始化计数器:
    • 等待线程阻塞
    • 任务完成通知
    • 唤醒等待进程

synchronizedreentrantlock区别及其应用场景

实现线程同步的方法

synchronized锁静态方法和普通方法的区别

可重入锁如何理解

synchronized支持重入吗?如何实现?

synchronized锁升级的过程是什么

JVM是如何优化synchronized

AQS是什么

AQS和CAS的区别

AQS如何实现一个可重入的公平锁

ThreadLocal作用和原理

悲观锁和乐观锁的区别

Java中实现一个乐观锁有哪些方式

CAS的缺点以及为什么不能所有的锁都用CAS

CAS的问题以及Java是如何解决的

voliatle关键字有什么作用?是否可以保证线程安全

voliatlesynchronized的比较

指令重排序的原理是什么

公平锁和非公平锁是什么

非公平锁吞吐量为什么比公平锁大

Synchronized是公平锁吗

ReentrantLock是怎么实现公平锁的

什么情况会产生死锁问题,改如何解决?


线程池

线程池如何使用

线程池的工作原理

线程池的参数如何设置

线程池工作队列满了如何拒接

核心参数是否可以设置为0

线程池的种类

线程池和三个线程同时并发相比有什么优势

线程池相关的设计模式

shutdown()shutdownNow()的方法作用

提交给线程池的任务可以被撤回吗


场景题

线程池打印奇偶数,如何控制打印顺序

单例模型以及用了synchronized为什么还要使用volatile

三个线程并发执行,一个线程等待三个线程执行完后再执行如何实现

假设两个线程并发读写同一个整型变量,初始值为零,每个线程加50次,结果可能是什么