多线程知识点方便面试

  •  
  • 线程、进程、管程的区别
  • 线程的状态
  • 线程状态如何流转
  • 说说你知道的线程池
  • 线程池的原理
  • 线程池的参数
  • ThreadLocal
  • Java中Runnable和Callable有什么不同
  • Thread 类中的start() 和 run() 方法有什么区别?
  • 如何在Java中实现线程
  • 如何停止一个线程
  • 如何在两个线程间共享数
  • 为什么wait, notify 和 notifyAll这些方法不在thread类里面
  • Java中interrupted 和 isInterruptedd方法的区别?
  • Java中堆和栈有什么不同?
  • 怎么检测一个线程是否拥有锁?
  • Thread类中的yield方法有什么作用
  • 你对线程优先级的理解是什么
  • 什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing)
  • Java中的fork join框架是什么?
  • Java多线程中调用wait() 和 sleep()方法有什么不同?
  • 什么是原子操作?在Java Concurrency API中有哪些原子类(atomic classes)?
  • 什么是Callable和Future?
  • 2阻塞队列都有什么,是什么数据结构,有什么特性,如何扩容
  • AQS的原理是什么
  • volatile关键字的作用及实现原理
  • 什么是CAS
  • ReadWriteLock是什么
  • FutureTask是什么
  • synchronized和ReentrantLock的区别及各自实现原理
  • synchronized锁升级
  • CyclicBarrier和CountDownLatch的区别
  •  CountDownLatch、CyclicBarrier、Semaphore、Exchanger原理
  • 四种线程池的创建:
  •  StampdedLock原理
  • CompletableFuture原理
  • LongAdder原理
  • LongAccumulator原理
  • Fork/Join原理
  • Condition原理

 

 

  1. 线程、进程、管程、协程的区别
  • 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位
  • 进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用
  • 管程是指管程实际上是定义了一个数据结构和在该数据结构上的能为并发进程所执行的一组操作,这组操作能同步进程和改变管程中的数据。
  • 协程是一种用户态的轻量级线程。协程的调度由用户控制,**拥有自己独立的寄存器上下文和栈。**协程的切换效率比线程还要高!

   2.线程的状态

就绪、运行、阻塞、等待、死亡

3.线程状态如何流转

640?wx_fmt=jpeg

4.说说你知道的线程池

  • newCachedThreadPool创建一个可缓存线程池--ThreadPoolExecutor
  • newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数。--ThreadPoolExecutor
  • newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。--ScheduledThreadPoolExecutor 只有核心线程数,若装不下报错
  • newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务。--ThreadPoolExecutor
  • newWorkStealingPool 创建一个具有抢占式操作的线程池---ForkJoinPool

5.ThreadPoolExecutor线程池的原理

线程池状态有

  1. RUNNING 111 表示正在运行
  2. SHUTDOWN 000 表示拒绝接收新的任务
  3. STOP 001 表示拒绝接收新的任务并且不再处理任务队列中剩余的任务,并且中断正在执行的任务。
  4. TIDYING 010 表示所有线程已停止,准备执行terminated()方法。
  5. TERMINATED 011 表示已执行完terminated()方法。

流程:

  1. 线程池的线程数量小于corePoolSize核心线程数量,开启核心线程执行任务。
  2. 线程池的线程数量不小于corePoolSize核心线程数量,或者开启核心线程失败,尝试将任务以非阻塞的方式添加到任务队列。
  3. 任务队列已满导致添加任务失败,开启新的非核心线程执行任务,若开启失败则做拒绝策略。
  4. 先尝试在线程池运行状态为RUNNING并且线程数量未达上限的情况下通过CAS操作将线程池数量+1,接着在ReentrantLock同步锁的同步保证下判断线程池为运行状态,然后把Worker添加到HashSet workers中。如果添加成功则执行Worker的内部线程。通过Worker执行任务,Worker构造方法指定了第一个要执行的任务firstTask,并通过线程池的线程工厂创建线程
  5. 在执行每个任务前通过lock方法加锁,执行完后通过unlock方法解锁,这种机制用来防止运行中的任务被中断。在执行任务时先尝试获取firstTask,即构造方法传入的Runnable对象,然后尝试从getTask方法中获取任务队列中的任务。在任务执行前还要再次判断线程池是否已经处于STOP状态或者线程被中断
  6. 如果指定时间内仍然没有任务可以执行,则进入销毁逻辑调用processWorkerExit()方法。
  7. getTask()方法通过一个循环不断轮询任务队列有没有任务到来,首先判断线程池是否处于正常运行状态,根据超时配置有两种方法取出任务:BlockingQueue.poll 阻塞指定的时间尝试获取任务,如果超过指定的时间还未获取到任务就返回null。BlockingQueue.take 这种方法会在取到任务前一直阻塞。

6.ThreadPoolExecutor线程池的参数

核心线程数、最大线程数、非核心线程存活时间、时间单位、线程生成工厂、拒绝策略、任务队列

拒绝策略:

①CallerRunsPolicy-该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。

②AbortPolicy-该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。

③DiscardPolicy-该策略下,直接丢弃任务,什么都不做。

④DiscardOldestPolicy-该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列

7.ForkJoinPool线程池

如果你的计算比较小,或者不是CPU密集型的任务,不太建议使用并行处理

ForkJoinPool 的每个工作线程都维护着一个工作队列(WorkQueue),这是一个双端队列(Deque),里面存放的对象是任务(ForkJoinTask)。
每个工作线程在运行中产生新的任务(通常是因为调用了 fork())时,会放入工作队列的队尾,并且工作线程在处理自己的工作队列时,使用的是 LIFO 方式,也就是说每次从队尾取出任务来执行。
每个工作线程在处理自己的工作队列同时,会尝试窃取一个任务(或是来自于刚刚提交到 pool 的任务,或是来自于其他工作线程的工作队列),窃取的任务位于其他线程的工作队列的队首,也就是说工作线程在窃取其他工作线程的任务时,使用的是 FIFO 方式。
在遇到 join() 时,如果需要 join 的任务尚未完成,则会先处理其他任务,并等待其完成。
在既没有自己的任务,也没有可以窃取的任务时,进入休眠。

8.ThreadLocal

ThreadLocal采用了“以空间换时间”的方式。他为每一个线程都提供了一份变量副本,因此可以同时访问而互不影响,所以肯定线程安全。

将数据放入ThreadLocalMap(Entry来保存数据,而且还是继承的弱引用)中在Entry内部使用ThreadLocal作为key,使用我们设置的value作为value。

要用remove 防止内存溢出

9.Java中Runnable和Callable有什么不同

Callable能返回数据和异常

10.Thread 类中的start() 和 run() 方法有什么区别?

调用start( )方法后,线程的状态是就绪状态

run( )其实是一个普通方法,只不过当线程调用了start( )方法后,一旦线程被CPU调度,处于运行状态,那么线程才会去调用这个run()方法,并且run()方法可以多次调用

11.如何在Java中实现线程

实现runnable和callable接口或者继承Thread类

12.如何停止一个线程

  • 使用退出标志,使bai线程du正常退出,也就是当run方法zhi完成后线程终止。
  • 使用stop方法强行终止线程
  • 使用interrupt方法中断线程。

13.如何在两个线程间共享数据

  • 如果每个线程执行的代码相同,可以使用同一个Runnable对象
  • 如果每个线程执行的代码不同,这时候需要用不同的Runnable对象

14.为什么wait, notify 和 notifyAll这些方法不在thread类里面

由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中,因为锁属于对象

15.Java中interrupted 和 isInterruptedd方法的区别?

interrupted()是静态方法:内部实现是调用的当前线程的isInterrupted(),并且会重置当前线程的中断状态

isInterrupted()是实例方法,是调用该方法的对象所表示的那个线程的isInterrupted(),不会重置当前线程的中断状态

16.Java中堆和栈有什么不同?

17.怎么检测一个线程是否拥有锁?

Thread.holdsLock()

18.Thread类中的yield方法有什么作用

暂停当前正在执行的线程对象,并执行其他线程

19.你对线程优先级的理解是什么

线程优先级是一个 int 变量(从 1-10),1 代表最低优先级,10 代表最 高优先级。java 的线程优先级调度会委托给操作系统去处理,所以与具体的操作系统优先级 有关,如非特别需要,一般无需设置线程优先级

20.什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing)

线程调度器是一个操作系统服务,它负责为 Runnable 状态的线程分配 CPU 时间。

一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。时间分 

片是指将可用的 CPU 时间分配给可用的 Runnable 线程的过程。分配 CPU 时间可 

以基于线程优先级或者线程等待的时间。线程调度并不受到 Java 虚拟机控制,所 

以由应用程序来控制它是更好的选择(也就是说不要让你的程序依赖于线程的优先级)

21.Java中的fork join框架是什么?

Fork/Join框架是Java 7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。Fork/Join框架要完成两件事情:

1.任务分割:首先Fork/Join框架需要把大的任务分割成足够小的子任务,如果子任务比较大的话还要对子任务进行继续分割

2.执行任务并合并结果:

22.Java多线程中调用wait() 和 sleep()方法有什么不同?

sleep是线程静态方法,wait是Object方法; 对锁的处理机制不同,sleep不会释放锁,而wait会释放锁; 使用区域不用,sleep是哪都能用,wait只能在同步方法或者同步代码块中

23.什么是原子操作?在Java Concurrency API中有哪些原子类(atomic classes)?

原子类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference

原子数组:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray

原子属性更新器:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater

解决ABA问题的原子类:AtomicMarkableReference(通过引入一个boolean来反映中间有没有变过),AtomicStampedReference(通过引入一个int来累加来反映中间有没有变过)

24.什么是Callable和Future?

Future 接口表示异步任务,是还没有完成的任务给出的未来结果。所以说 Callable

用于产生结果,Future 用于获取结果。

25.阻塞队列都有什么,是什么数据结构,有什么特性,如何扩容

  • ArrayBlockingQueue :是最典型的有界队列,内部是用数组存储元素的,利用 ReentrantLock实现线程安全。创建时第一个参数是容量,第二个参数是是否公平

  • LinkedBlockingQueue:内部用链表实现的。如果创建时不指定它的初始容量,那么它容量默认就为整型的最大值 Integer.MAX_VALUE,利用 ReentrantLock实现线程安全

  • SynchronousQueue: 最大的不同之处在于,它的容量为 0,所以没有一个地方来暂存元素,导致每次取数据都要先阻塞,直到有数据被放入,相反,每次放数据的时候也会阻塞,直到有消费者来取。公平是用TransferQueue(FIFO),非公平TransferStack(双栈)实现  CAS //   栈中节点的几种类型:1. 消费者(请求数据的) static final int REQUEST = 0; // 2. 生产者(提供数据的) static final int DATA = 1; // 3. 二者正在匹配中 static final int FULFILLING = 2;

TransferStack(双栈)实现:

栈中节点的几种类型:

  1. 消费者(请求数据的) static final int REQUEST = 0;
  2. 生产者(提供数据的) static final int DATA = 1;

  3. 二者正在匹配中 static final int FULFILLING = 2;

流程:

(1)如果栈中没有元素,或者栈顶元素跟将要入栈的元素模式一样,就入栈;

(2)入栈后自旋等待一会看有没有其它线程匹配到它,自旋完了还没匹配到元素就阻塞等待;

(3)阻塞等待被唤醒了说明其它线程匹配到了当前的元素,就返回匹配到的元素;

(4)如果两者模式不一样,且头节点没有在匹配中,就拿当前节点跟它匹配,匹配成功了就返回匹配到的元素;

(5)如果两者模式不一样,且头节点正在匹配中,当前线程就协助去匹配,匹配完成了再让当前节点重新入栈重新匹配;

TransferQueue实现

流程:

1. 若队列为空 / 队列中的尾节点和自己的 类型相同, 则添加 node
   到队列中, 直到 timeout/interrupt/其他线程和这个线程匹配
   timeout/interrupt awaitFulfill方法返回的是 node 本身
   匹配成功的话, 要么返回 null (producer返回的), 或正真的传递值 (consumer 返回的)

2. 队列不为空, 且队列的 head.next 节点是当前节点匹配的节点,
   进行数据的传递匹配, 并且通过 advanceHead 方法帮助 先前 block 的节点 dequeue

1. 一开始整个queue为空, 线程直接封装成QNode, 通过 awaitFulfill 方法进入自旋等待状态, 除非超时或线程中断, 不然一直等待, 直到有线程与之匹配
2. 下个再来的线程若isData与尾节点一样, 则进行第一步, 不然进行数据转移(步骤 21), 然后 unpark 等待的线程
3. 等待的线程被唤醒, 从awaitFulfill方法返回, 最后将结果返回

1. 计算timeout时间(若 time = true)
2. 判断 当前节点是否是 head.next 节点(queue中有个dummy node 的存在, AQS 中也是这样), 若是的话就进行 spin 的赋值, 其他的节点没有这个需要, 浪费资源
3. 接下来就是自旋, 超过次数就进行阻塞, 直到有其他线程唤醒, 或线程中断(这里线程中断返回的是 Node 自己)

clean 方法是 整个代码分析过程中的难点:

1. 删除的节点不是queue尾节点, 这时 直接 pred.casNext(s, s.next) 方式来进行删除(和ConcurrentLikedQueue中差不多)
2. 删除的节点是队尾节点
  1) 此时 cleanMe == null, 则 前继节点pred标记为 cleanMe, 为下次删除做准备
  2) 此时 cleanMe != null, 先删除上次需要删除的节点, 然后将 cleanMe至null, 让后再将 pred 赋值给 cleanMe
这时我们想起了 ConcurrentSkipListMap 中的 marker 节点, 对, marker 和 cleanMe 都是起着防止并发环境中多删除节点的功能
  • PriorityBlockingQueue: 是一个支持优先级的无界阻塞队列,数组实现,默认是11个,可以通过自定义类实现 compareTo() 方法来指定元素排序规则,或者初始化时通过构造器参数 Comparator 来指定排序规则。同时,插入队列的对象必须是可比较大小的,也就是 Comparable 的,否则会抛出 ClassCastException 异常。扩容:如果队列元素数>=64 则扩大到1.5倍,若<64扩大到2倍+2

  • DelayQueue是一个具有“延迟”功能的队列,这种队列是有序的,越靠近队列头代表越早过期。DelayQueue用于放置实现了Delayed接口的对象,Delayed 接口继承自 Comparable,里面的getDelay需要我们实现,getDelay 方法返回的是“还剩下多长的延迟时间才会被执行”,如果返回 0 或者负数则代表任务已过期,才能从队列中取走。底层用PriorityQueue(数组)实现。,逻辑上要判断是否可以执行Delayed中的getDelay()

  • LinkedTransferQueue

    由链表组成的无界队列。比其他队列多了两个方法:tryTransfer、transfer,采用一种预占模式。意思就是消费者线程取元素时,如果队列不为空,则直接取走数据,若队列为空,那就生成一个节点(节点元素为null)入队,然后消费者线程被等待在这个节点上,后面生产者线程入队时发现有一个元素为null的节点,生产者线程就不入队了,直接就将元素填充到该节点,并唤醒该节点等待的线程,被唤醒的消费者线程取走元素,从调用的方法返回。我们称这种节点操作为“匹配”方式。

  •  LinkedBlockingDeque

    链表组成的双端队列。这个队列在以后凯哥讲For/Join框架的时候,还会说到

26.AQS的原理是什么

尝试加锁判断锁状态若加锁个数为0直接执行逻辑获取了锁,若不为0判断是否是自己的线程是的话锁个数加1

上述条件不满足,生成一个node加入等待队列中(自旋加入队尾)

尝试获取锁,判断当前节点的前节点是否是头节点若是获取锁获取成功替换当前节点为头节点,没有判断当前节点的等待状态来去等是否需要等待

NODE有共享模式和独占模式,独占模式为NULL。

NODE有CANCELLED,SIGNAL,SIGNAL,PROPAGATE4中状态值。

SIGNAL:节点释放锁,是否需要唤醒后继节点
CANCELLED:单节点处于这个状态,将被移除到等待队列
CONDITION: 处于这个状态的节点线程,放在条件队列中。它永远不会被
用作一个同步队列节点,直到等待的条件发生,节点将被转移到同步队列中。
(这个状态与其他状态,没有关联,只是一种简化的机制)。
PROPAGATE: 处于此模式下,释放共享锁具有传递性。头节点调用
doReleaseShared方法,保证传递释放共享锁,即使有其他的操作干涉。
这个时共享模式下的状态。

27.volatile关键字的作用及实现原理

作用:保持可见性、防止指令重拍

  • 每次读取前必须先从主内存刷新最新的值。
  • 每次写入后必须立即同步回主内存当中

volatile关键字通过“内存屏障”来防止指令被重排序&&Happens-Before内存模型

  • 在每个volatile写操作的前面插入一个StoreStore屏障。
  • 在每个volatile写操作的后面插入一个StoreLoad屏障。
  • 在每个volatile读操作的后面插入一个LoadLoad屏障。
  • 在每个volatile读操作的后面插入一个LoadStore屏障。

28.什么是CAS

29.ReadWriteLock是什么

特性:公平&非公平、重入、锁降级

 

FutureTask是什么

synchronized和ReentrantLock的区别及各自实现原理

synchronized锁升级

CyclicBarrier和CountDownLatch的区别

 CountDownLatch、CyclicBarrier、Semaphore、Exchanger原理

四种线程池的创建:

 StampdedLock原理

CompletableFuture原理

LongAdder原理

LongAccumulator原理

Fork/Join原理

Condition原理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值