- 多线程知识点概述
1.什么是多线程
1)一个进程中往往有多条程序执行路径,该程序可以理解为多线程程序
2)java程序的入口是main方法,从操作系统的角度看,main方法的启动意味着启动了一个java进程。main方法所在的程序执行路径即为主线程,同时,main线程的启动,会伴随着其他线程的创建和启动,如垃圾回收线程等
3)线程的创建者:主线程的创建者是jvm,普通线程可以通过调用JDK的相关API进行创建
2.多线程的作用
1)更大的吞吐量
2)提升性能
3.多线程的基本使用(代码:test219_thread/test/demo1)
1)线程任务的定义
2)线程的创建和启动
4.多线程带来的问题(代码:test219_thread/test/demo2)
1)线程安全问题(本质上的体现是线程之间的共享数据出现不正确的结果)
2)活跃性问题(死锁、饥饿、活锁以及丢失信号)
3)性能问题(线程的创建和销毁、线程的上下文切换)
5.线程安全描述的对象(代码:test219_thread/test/demo3)
1)类线程安全
2)程序线程安全
6.JDK提供几种方式来保证线程安全,分别是:synchronized、volatile、final、CAS、ThreadLocal
7.线程的状态,官方定义的方式有六种,定义在枚举java.lang.Thread.State中
1)新建状态: Thread.State.NEW (Thread t = new Thread();)
2)可运行状态: Thread.State.RUNNABLE (包括两种:在等待CPU分配时间片、运行状态)
3)阻塞状态: Thread.State.BLOCKED (线程在等待同步锁,试图进入同步代码块、一种是IO阻塞)
4)等待状态: Thread.State.WAITING
等待状态意味着线程放弃CPU的执行资格,退出CPU的线程处理队列,即向CPU发出一个信号,不需要分配时间片给这个线程。同时释放锁。需要其他线程调用notify方法唤醒,然后转变为可运行状态,继续进入CPU的线程处理队列
5)计时等待状态:Thread.State.TIMED_WAITING
计时等待状态意味着线程放弃CPU的执行资格,退出CPU的线程处理队列,即向CPU发出一个信号,不需要分配时间片给这个线程。不会释放锁。直到设置的睡眠时间结束之后,然后转变为可运行状态,继续进入CPU的线程处理队列
6)终止状态: Thread.State.TERMINATED
8.wait/notify机制(代码:test219_thread/test/demo4)
1)涉及到的方法
· wait():让线程进入等待状态,退出CPU的线程处理队列,释放锁,线程被存储到对象obj的线程等待集中
· notify():唤醒对象obj的线程等待集中的任意一个线程
· notifyAll():唤醒对象obj的线程等待集中的所有线程
2)注意点
· 这几个方法必须定义在同步块或者同步方法中,因为这些方法是用于操作线程状态的方法,必须要明确到底操作的是哪个锁上的线程
· 这几个方法定义在Object中的原因:因为这几个方法是对象监视器的方法,对象监视器其实就是同步锁,同步锁可以是任意对象,所以任意对象都可以调用的方法只能定义在Object中
9.Java内存模型:假如有一个线程的共享变量,这个变量是存在于主内存中的,但是为了提高效率,每个线程在自己的工作内存中有一份该变量的拷贝。(主内存映射到硬件可以理解为平常说的内存,而工作内存可以理解为CPU中的高速缓冲存储器,CPU从高速缓冲存储器中读数据肯定要比从内存中读数据要快得多)
10.中断机制(代码:test219_thread/test/demo5)
1)在Java中,停止一个线程的主要机制是中断,中断并不是强迫终止一个线程,它是一种协作机制,是给线程传递一个取消信号,但是由线程来决定如何以及何时退出
2)通过Thread类的interrupt()方法来向线程发出中断请求
3)线程在不同状态下对于中断请求所产生的反应
· 如果一个线程处于新建状态或者终止状态,对于中断请求几乎是屏蔽的
· 如果一个线程处于RUNNABLE状态或者阻塞状态,对于中断请求只是设置中断标志位并没有强制终止线程
· 如果一个线程处于睡眠状态或等待状态,对于中断请求是敏感的,线程会从睡眠状态或等待状态强制恢复到可运行状态,重新进入到CPU的线程处理队列,同时线程会抛出中断异常并清空中断标志位
4)关于设置中断标志位和清空中断标志位
· 设置中断标志位:指的是把中断状态设置为true,即Thread.currentThread().isInterrupted()返回true
· 清空中断标志位:指的是把中断状态设置为false,即Thread.currentThread().isInterrupted()返回false
5)中断机制的应用
· 对于处于RUNNABLE状态或者阻塞状态的线程,可以通过判断中断状态来决定线程是否要终止
· 对于处于睡眠状态或等待状态的线程,可以通过捕获中断异常之后来决定线程是否要终止
6)与线程中断有关的三个方法:interrupt()、interrupted()、islnterrupted()。(均在Thread类中)
11.juc包
1)AQS
2)Lock
3)并发工具类
4)原子类
- 解决线程安全问题的方式
一、synchronized(代码:test219_thread/test/demo6)
1)synchronized的基本使用
· 修饰语句块
· 修饰方法
2)synchronized的实现原理
· 修饰语句块
· 修饰方法
3)synchronized锁的升级
4)synchronized实现可重入的原理
· 每一个锁关联一个线程持有者和计数器,当计数器为0时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为0,则释放该锁
5)synchronized与wait/notify机制
6)验证同步方法中的锁对象
· 非静态方法的锁对象为this
· 静态方法的锁对象为Class对象
二、volatile(代码:test219_thread/test/demo7)
三、final
四、CAS
· 主要是通过sun.misc.Unsafe来实现CAS
五、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,ThreadLocal 用于实现线程隔离,而 synchronized 用于实现线程同步
- juc包
一、AQS
二、Lock
三、并发工具类(代码:test219_thread/test/demo9)
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 阻塞的是其它线程,下一步动作的执行者是其它线程,可重复使用
四、原子类(代码:test219_thread/test/demo10)
1)原子类的出现背景:当一个线程更新一个变量时,程序如果没有正确的同步,那么这个变量对于其他线程来说是不可见的。我们通常使用 synchronized 或者 volatile 来保证线程安全的更新共享变量。在JDK1.5中,提供了 java.util.concurrent.atomic 包,这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式
2)原子类的应用案例:多个线程进行 i++ 操作,为了线程安全,需要加 synchronized 锁,锁是比较重的,因此可以考虑使用原子类AtomicInteger 来代替 synchronized
3)原子类的底层实现是基于 volatile 和 CAS,因此可以减少锁带来的性能开销
- 多线程在实际业务场景中的使用
1.基本使用
1)主调用线程需要等待各个线程的返回结果(代码:test/demo11/ThreadInActualBusiness.needThreadResult())
2)主调用线程不需要等待各个线程的返回结果(代码:test/demo11/ThreadInActualBusiness.main())
(这种场景用得相对较少,因为会出现:接口已经完成响应,但是业务处理还没有结束的情况)
线程池
4.线程池
1)线程池的顶层接口和类
· Executor:Executor 是一个接口,定义了一个基本的执行方法 execute(Runnable command),用于执行给定的 Runnable 任务。它是所有线程池执行机制的基础
· ExecutorService:ExecutorService 也是接口,它扩展了 Executor 接口,提供了更丰富的线程池控制方法,如 submit(),以及线程池的关闭方法 shutdown() ,ExecutorService 是创建和管理线程池的主要接口
· AbstractExecutorService:实现了 ExecutorService 接口的部分方法,为创建具体的线程池服务提供了基础框架。它提供了线程池的一些通用实现,如关闭线程池的行为
· ThreadPoolExecutor:ThreadPoolExecutor 是 AbstractExecutorService 的一个具体实现,提供了完整的线程池实现。它允许开发者精细控制线程池的参数,如核心线程数、最大线程数、工作队列类型、线程工厂、拒绝策略等。ThreadPoolExecutor 是 Java 中线程池的核心实现类
2)线程池的使用方式
· 直接创建 ThreadPoolExecutor
· 通过 Executors 工具类创建线程池(本质上也是创建 ThreadPoolExecutor)
static ExecutorService newFixedThreadPool(int nThreads)
static ExecutorService newCachedThreadPool()
static ExecutorService newSingleThreadExecutor()
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
实现多线程的方式
1)继承 Thread 类
2)实现 Runnable 接口
3)实现 Callable 接口 + FutureTask
4)线程池(ExectutorService 结合 Callable、Future 实现有返回结果的多线程)