一、基本知识
1.多线程的作用
2.定义线程任务的方式
1)Runnable,实现 Runnable 接口,重写 run() 方法
2)Thread,继承 Thread 类,重写 run() 方法
3)Callable,实现 Callable 接口,重写 call() 方法
3.开启新线程的方式
开启新线程有两层含义:创建新线程和启动新线程
1)Thread 类
2)线程池(线程池的底层实际上也是通过 Thread 类创建和启动线程)
3)CompletableFuture(CompletableFuture 的底层实际上也是通过 Thread 类创建和启动线程)
4.异步的实现方式
1)Thread
2)线程池
3)CompletableFuture
5.线程安全
1)线程安全是指什么
多个线程在同时执行同一段代码或者说在访问共享资源时,出现了异常现象。比如说 xxx
2)保证线程安全的方式
· synchronized
· ReentrantLock
· CAS
· volatile
· ThreadLocal
二、线程池(ThreadPoolExecutor)
1.线程池七个参数
1)corePoolSize,线程池核心线程数
2)maximumPoolSize 线程池最大线程数量
3)keepAliveTime 非核心线程存活时间
4)unit 非核心线程存活时间单位
5)workQueue 工作队列
· ArrayBlockingQueue:基于数组的有界阻塞队列
· LinkedBlockingQuene:基于链表的无界阻塞队列
· SynchronousQuene:一个不缓存任务的阻塞队列
· PriorityBlockingQueue:具有优先级的无界阻塞队列
6)threadFactory:线程工厂,创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等
7)handler 拒绝策略
· CallerRunsPolicy:在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务
· AbortPolicy:直接丢弃任务,并抛出RejectedExecutionException异常
· DiscardPolicy:直接丢弃任务,什么都不做
· DiscardOldestPolicy:抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列
2.工作机制:最开始启用核心线程处理任务,当所有核心线程全部都在运行,并且有新的任务进来的时候,这些任务会放入阻塞队列中,如果阻塞队列满了,就会启动非核心线程处理任务,如果所有的线程都在运行,并且阻塞队列满的时候,会启用拒绝策略,拒绝策略有4种,默认的拒绝策略是直接抛出异常
三、保证线程安全的机制
1.synchronized
1)作用:可以保证原子性(一段代码以原子的方式执行,不会执行到一半就中断)、可见性、有序性
2)使用方式:可以修饰方法、修饰代码块
3)底层实现原理:synchronized 属于jvm层面的,如果修饰的是代码块,则会生成两条jvm字节码指令 monitorenter 和 monitorexit,monitorenter指向代码块开始的位置,monitorexit 指向代码块结束的位置;如果修饰的是方法,则会生成一个 ACC_SYNCHRONIZED 标识符,标识这个方法是一个同步方法
4)锁的性质:非公平锁、悲观锁、可重入锁
5)锁升级的过程:在JDK1.6之前,synchronized被认为是重量级锁,在JDK1.6之后,引入了偏向锁和轻量级锁来减少获得锁和释放锁带来的性能消耗。锁的状态有四种,分别是无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,当没有线程持有锁的时候,它是无锁状态,当有一个线程持有锁的时候,它会改成偏向锁状态,如果没有其它线程来争夺这个锁,那么就一直处于偏向锁状态,当有其它线程来争夺锁的时候,这把锁就会改成轻量级锁状态,其它获取不到锁的线程一直在自旋,当自旋的次数达到一定次数还是获取不到锁的话,它就会认为自旋的成本和代价太高昂了,浪费CPU的资源,因此这把锁就会膨胀为重量级锁
6)是如何实现可重入的:每一个锁关联一个线程持有者和计数器,当计数器为0时表示锁是空闲的,当某个线程请求获取锁成功之后,计数器加1,当这个线程再次申请锁时,计数器继续加1,当退出同步代码块时,计数器会递减,当计数器为0时则表示释放锁
2.ReentrantLock
3)底层实现:基于 AQS 实现
3.CAS
1.什么是 CAS
1)CAS是一种乐观锁机制,指的是比较并交换,有的称比较并设置,意思都一样,它是这样的:现在要去改变一个变量的值,在改变之前先把这个值记录下来,这个值称为旧值,然后进行设置新值的时候,会先判断一下这个旧值有没有改变,如果没有改变的话就可以设置成功,如果发生改变的话就放弃修改
2)CAS的底层实现
CAS的底层实现是基于CPU原语来保证操作的原子性。在JDK层面,提供了Unsafe这个类来进行CAS的操作
在Java层面,CAS相关的接口由sun.misc.Unsafe类提供,其中CAS相关的方法都是native方法
4、volatile
1)前置知识
在理解 volatile 之前,有必要先了解 Java 内存模型和 happens-before 规则
2)java 内存模型:假如有一个线程的共享变量,这个变量是存在于主内存中的,但是为了提高效率,每个线程在自己的工作内存中有一份该变量的拷贝。(主内存映射到硬件可以理解为平常说的内存,而工作内存可以理解为CPU中的高速缓存存储器,CPU从高速缓存存储器中读数据肯定要比从内存中读数据要快得多)
3)happens-before 规则:
4)作用:可以保证可见性、有序性
· 可见性:当一个线程修改了一个 volatile 变量的值,其他线程能够立即看到这个修改
· 有序性:volatile 通过禁止指令重排序来保证代码的顺序执行,即 volatile 变量的读写操作不会被编译器和处理器重排序
5)实现原理
· 可见性:可见性是基于缓存一致性协议实现。当一个线程写入 volatile 变量时,它会强制将线程工作内存中的变量值刷新到主内存。当其他线程读取这个变量时,它们必须从主内存中读取该值,而不是从自己的工作内存中读取可能过时的副本
· 有序性是基于内存屏障(LoadLoad Barriers、LoadStore Barriers、StoreStore Barriers、StoreLoad Barries)实现的
6)使用方式
5.ThreadLocal
1.ThreadLocal 是什么
1)ThreadLocal 直译为线程本地,也称为线程本地变量
2)意思是在 ThreadLocal 变量中填充的值只属于当前线程,对其它线程是不可见的,从而起到线程隔离的作用,避免了线程安全问题
2.ThreadLocal 的作用(为什么需要 ThreadLocal)
1)在线程之间隔离数据,对于同一个变量,每个线程都有独立的数据
2)减少锁的使用,如果没有 ThreadLocal,在某些并发场景下需要加锁来解决
3.ThreadLocal 的常用方法
· ThreadLocal() 创建 ThreadLocal 对象
· set(T value) 设置当前线程绑定的局部变量
· T get() 获取当前线程绑定的局部变量
· remove() 移除当前线程绑定的局部变量
4.ThreadLocal 的基本使用(代码:test219_thread/test/demo8)
5.ThreadLocal 常见的应用场景
1)web 请求,在 Controller 解析出用户信息后放入 ThreadLocal 中,在 Service 层或者其它位置可以直接通过 ThreadLocal 获取到用户信息
2)JDBC,一个线程中需要多次操作数据库,并且需要保证线程内的事务,可以在操作数据库之前把数据库连接放入 ThreadLocal 中,后续在本线程的任意方法都可以通过 ThreadLocal 获取到数据库连接
(共同点都是需要在一个线程内的不同方法获取某一类型(用户信息、数据库连接)的数据,通过使用 ThreadLoca 就可以减少参数在方法之间的传递)
6.ThreadLocal 的实现原理
1)ThreadLocal、ThreadLocalMap、Thread 三者关系
· ThreadLocalMap 是 ThreadLocal 的内部类,ThreadLocalMap 通过 Entry[] 来存储数据,Entry 是 ThreadLocalMap 的内部类,key 为 ThreadLocal 对象(实际上 key 为指向 ThreadLocal 实例的弱引用),value 为 set 进 ThreadLocal 的值
· Thread 持有 ThreadLocalMap,变量名称为 threadLocals
2)数据流转过程
· 调用 set() 方法时,先获取当前线程的 ThreadLocalMap 对象,如果 ThreadLocalMap 为空则创建一个,然后把 ThreadLocal 对象和 set 进 ThreadLocal 的值放入 ThreadLocalMap 的 Entry[] 中
· 调用 get() 方法时,先获取当前线程的 ThreadLocalMap 对象,然后从 ThreadLocalMap 中根据 ThreadLocal 对象(应该是计算哈希值再取模)找到对应的 Entry,获取 Entry 的 value 返回
· 调用 remove() 方法时,先获取当前线程的 ThreadLocalMap 对象,然后从 ThreadLocalMap 中根据 ThreadLocal 对象找到对应的Entry 删除
7.ThreadLocal 内存泄露问题
1)ThreadLocal 内存泄露是指 ThreadLocalMap 中的 value 没办法被回收
2)内存泄露原因:
ThreadLocal 已经使用结束了(意味着没有强引用指向堆中的 ThreadLocal 对象),而线程还存活着,JVM 在进行垃圾回收后会把只有弱引用指向的 ThreadLocal 对象回收,也就是 Entry 的 key 会被回收,但是此时 value 还在,因此就产生了内存泄露
3)如何避免内
内存泄露:在使用完 ThreadLocal 之后,调用 remove() 方法把 Entry 置空
8.ThreadLocal 的最佳实践
· 避免过度使用 ThreadLocal
· 使用完 ThreadLocal 要调用 remove 方法进行清除,以避免内存泄露
9.ThreadLocal 有什么缺点,如何改进
10.零碎问题
1)ThreadLocal 对象在业务代码中被创建,该对象在两个地方被引用,被业务代码强引用、被 ThreadLocalMap 的 key 弱引用
2)弱引用的特点
:只要进行垃圾回收,不管 jvm 内存是否充足,都会回收只被弱引用的对象(如果对象同时有强弱引用时,则不回收)
3)key 设计成弱引用是为了垃圾回收时可以保证 key 指向的 ThreadLocal 对象被回收,而调用 remove() 方法是保证 Entry 中的 value 可以尽快被回收
4)在调用 get()、set() 方法的某些时候,会伴随调用 expungeStaleEntry() 方法把 key 为 null 的 Entry 置空,以此来尽可能避免内存泄露
5)如果线程执行完正常销毁,是不会存在内存泄露的,因为 Thread 对象被回收后,ThreadLocalMap 自然也会被回收
6)ThreadLocal 尤其是当与线程池结合使用时,由于线程需要被复用而无法销毁,因此使用完 ThreadLocal 如果没有及时清理,就会导致内存泄露
7)remove() 方法要放在 finally 中
8)ThreadLocalMap 使用开放寻址法解决哈希冲突
9)如果需要在父子线程之间传递 ThreadLocal 填充的数据,可以使用 InheritableThreadLocal
10)ThreadLocal 不能平替 synchronized,synchronized 用于实现线程同步,而 ThreadLocal 用于实现线程隔离
四、AQS
1.AQS 的核心组成部分
· volatile修饰的state变量:当它的值为0的时候,就表示资源是空闲的,当为1或者是其他数值的时候,就表示资源处于锁定状态
· 等待队列:它的底层数据结构是双向链表,那些获取不到资源的线程会被包装成一个Node节点,放到这个队列当中
· CAS:相当于是一种轻量级的并发处理,因为修改属性的时候是多个线程同时去修改的,但是最终只有一个线程能修改成功,修改失败的线程会通过CAS进行重试
2.AQS 的工作原理:假设一个线程来请求资源,这个资源是空闲的,那么请求资源成功的线程会被设定为工作线程,把资源的状态设定为锁定状态,这个时候如果再有线程来请求资源,那么就会请求失败,请求不到资源的线程会加入到同步队列中,当资源被释放之后,同步队列中的线程再次进行资源的争夺
五、常见并发工具类
1.基本概述
1)CountDownLatch、CyclicBarrier、Semaphore 可以认为是一种控制并发流程的工具类
2)Exchanger 可以认为是线程间交换数据的工具类
2.CountDownLatch
1)CountDownLatch 的核心思想是通过计数器来控制线程的执行顺序,当计数器的值降为0时,所有等待的线程都会被唤醒,然后开始执行下一步操作
2)CountDownLatch 的使用:test219_thread/demo9/CountDownLatchDemo
3)CountDownLatch 的执行过程
· 在主调用线程中创建 CountDownLatch,并传入 count,count 通常为工作线程数量
· 在工作线程中调用 countDown() 方法,每调用一次 count 就减1
· 在主调用线程中调用 await() 方法来阻塞主调用线程
· count减到为0时,主调用线程被唤醒
4)CountDownLatch 的底层实现:基于 AQS 实现
3.CyclicBarrier
1)CyclicBarrer 的作用是让一组线程达到一个屏障(同步点)时被阻塞,直到所有的线程到达此屏障时,才会唤醒被屏障阻塞的所有线程
2)CyclicBarrer 的使用:test219_thread/demo9/CyclicBarrierDemo、CyclicBarrierDemo2、CyclicBarrierDemo3、CyclicBarrierDemo4
3)CyclicBarrer 的执行过程
· 在主调用线程中创建 CyclicBarrer,并传入 parties,parties 通常为工作线程数量
· 在工作线程中调用 await() 方法,每调用一次,就说明有一个线程抵达屏障,直到有 parties 个线程抵达屏障后,唤醒被屏障阻塞的所有线程
4)CyclicBarrier 的底层实现:基于 AQS 实现
4.Semaphore
1)信号量是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源
2)Semaphore 的使用:test219_thread/demo9/SemaphoreDemo
3)Semaphore 的工作流程
· 在主调用线程中创建 Semaphore,并传入 permits,permits 为许可证数量
· 在工作线程中调用 acquire() 方法,每调用一次,许可证数量就减1
,当许可证数量减到为0时,再调用 acquire() 会被阻塞,直到已经获得许可证的工作线程调用 release() 方法归还许可证,然后被阻塞的线程会获得许可证
4)Semaphore 的底层实现:基于 AQS 实现
5.Exchanger
Exchanger 的底层实现:使用 ThreadLocal 和 ArrayBlockingQueue 等数据结构来实现线程间的配对和数据交换
6.零碎问题
1)CountDownLatch 和 CyclicBarrier 的区别
· CountDownLatch 阻塞的是主线程,下一步动作的执行者是主线程,不可重复使用
· CyclicBarrier 阻塞的是其它线程,下一步动作的执行者是其它线程,可重复使用
六、原子类
1)原子类的出现背景:当一个线程更新一个变量时,程序如果没有正确的同步,那么这个变量对于其他线程来说是不可见的。我们通常使用 synchronized 或者 volatile 来保证线程安全的更新共享变量。在JDK1.5中,提供了 java.util.concurrent.atomic 包,这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式
2)原子类的应用案例:多个线程进行 i++ 操作,为了线程安全,需要加 synchronized 锁,锁是比较重的,因此可以考虑使用原子类AtomicInteger 来代替 synchronized
3)原子类的底层实现是基于 volatile 和 CAS,因此可以减少锁带来的性能开销
七、其它常见问题
1.synchronized
轻量级锁膨胀之后,就升级为重量级锁了。重量级锁是依赖对象内部的monitor锁来实现的,而monitor又依赖操作系统的 MutexLock (互斥锁)来实现的,所以重量级锁也被称为互斥锁和悲观锁
为什么说重量级锁开销大
当系统检查到锁是重量级锁之后,会把等待想要获得锁的线程进行阻塞,被阻塞的线程不会消耗CPU,但是阻塞或者唤醒一个线程时,都需要操作系统来帮忙,这就需要从用户态转换到内核态,而转换状态是需要消耗很多时间的,有可能比用户执行代码的时间还要长
适应性自旋锁
JDK1.6引入了自旋锁,并且带有自适应功能,称为自适应自旋锁。它的自旋次数是会变的,我用大白话来讲一下,就是线程如果上次自旋成功了,那么这次自旋的次数会更加多,因为虚拟机认为既然上次成功了,那么这次自旋也很有可能会再次成功。反之,如果某个锁很少有自旋成功,那么以后的自旋次数会减少甚至省略掉自旋过程,以免浪费处理器资源。大家现在觉得没这么low了吧
2.synchronized 和 ReentrantLock 的区别
3.什么是 JMM(Java 内存模型)
假如有一个线程的共享变量,这个变量是存在于主内存中的,但是为了提高效率,每个线程在自己的工作内存中有一份该变量的拷贝。(主内存映射到硬件可以理解为平常说的内存,而工作内存可以理解为CPU中的高速缓存存储器,CPU从高速缓存存储器中读数据肯定要比从内存中读数据要快得多)
4.happens-before 听过吗?
5.指令重排知道吗?
6.final 可以保证可见性吗?
7.并发类库提供的线程池实现有哪些?之间有什么区别?
8.CompletableFuture 有用过吗?
9.多个线程并发执行,如何控制它们的执行顺序?
10.Java 中的阻塞队列用过哪些?
11.用过哪些原子类?
12.用过累加器吗?
13.父子线程之间如何传递数据?
14.FastThreadLocal 的原理是?
15.TransmittableThreadLocal 听过吗?
16.Thread.sleep(0) 有什么作用?
17.wait、notify、notifyall 有什么作用?
18.DelayQueue 和 ScheduledThreadPool 有什么区别
19.StampedLock 用过吗?
20.ForkJoinPool 有了解过吗?
21.核心线程数如果等于0、核心线程数等于最大线程数(allowCoreThreadTimeOut=true 时,核心线程会被回收)、核心线程数大于最大线程数、最大线程数小于等于0会发生什么
22.如何实现两个线程轮流打印A1B2C3...
1)for 死循环
2)synchronized、wait()、notifyAll()
3)Lock 与 Condition
4)LockSupport
5)BlockingDeque
6)CyclicBarrier
参考:https://blog.csdn.net/Water_Sky/article/details/133204623
23.4个线程abcd,如何在abc执行完后再执行d
24.协程有听过吗