线程的创建方式
- implements runable
- implements callable
- extends thread
- jdk线程池、spring线程池 ...
线程的状态
- 新建
new - 就绪
start() - 运行
获得到时间片执行run() - 阻塞
-
blocking 等待获取到排他锁
-
Waiting 无限期等待
| 进入方法 | 退出方法 |
|---|---|
| 没有设置 Timeout 参数的 Object.wait() 方法 | Object.notify() / Object.notifyAll() |
| 没有设置 Timeout 参数的 Thread.join() 方法 | 被调用的线程执行完毕 |
| LockSupport.park() 方法 | - |
-
Timed Waiting 限期等待
| 进入方法 | 退出方法 |
|---|---|
| Thread.sleep() 方法 | 时间结束 |
| 设置了 Timeout 参数的 Object.wait() 方法 | 时间结束 / Object.notify() / Object.notifyAll() |
| 设置了 Timeout 参数的 Thread.join() 方法 | 时间结束 / 被调用的线程执行完毕 |
| LockSupport.parkNanos() 方法 | - |
| LockSupport.parkUntil() 方法 | - |
- 死亡
可以是线程结束任务之后自己结束,或者产生了异常而结束。
多线程安全问题的产生
-
可见性【缓存一致性】
-
- cpu缓存引起
-
有序性【指令重排序】
-
- 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
-
- 指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
-
- 内存系统的重排序。由于处理器使用缓存和读 / 写缓冲区,这使得加载和存储操作看上去可能是在乱序执行
-
原子性【非原子操作】
-
- 分时复用线程切换)引起,由于CPU分时复用(线程切换)的存在,线程1执行了第一条指令后,就切换到线程2执行,假如线程2执行了这三条指令后,再切换回线程1执行后续两条指令。一组指令的执行会被打断,转而去执行与上一组指令相关变量的指令。
如何解决原子性问题?
- atomic 的类利用cas实现原子操作
- 通过synchronized、reentrantLock等加锁,保证代码段只有一个线程执行,保证操作的原子性
如何解决可见性?
- volitile 关键字声明共享变量,通过禁止指令重排序和强制刷新cpu缓存来实现可见性、有序性
- 通过synchronized、reentrantLock等加锁,在锁释放的时候会将修改的值刷新到主内存
如何解决有序性?
- volitile 关键字声明共享变量,通过禁止指令重排序(内存屏障)和强制刷新cpu缓存(lock前缀)来实现可见性、有序性
- 通过synchronized、reentrantLock等加锁,保证代码段只有一个线程执行,保证操作的有序性
volatile 有序性和可见性详解?
- volatile 变量的写操作 之前 插入写屏障,保证写之前的所有指令都执行完毕;读操作之后插入读屏障,保证读操作之后的所有指令都不会被重新排序到读操作之前,保证有序性。
- volatile 关键字会禁止线程将变量存储在线程本地缓存(cpu缓存中)中,而是直接从主内存中读取和写入,保证可见性。
Happens-Before 8大规则?
- 单一线程原则(Single Thread rule),在一个线程内,在程序前面的操作先行发生于后面的操作。
- 管程锁定规则(Monitor Lock Rule),一个 unlock 操作先行发生于后面对同一个锁的 lock 操作。
- volatile变量规则(Volatile Variable Rule),对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作。
- 线程启动规则(Thread Start Rule),Thread 对象的 start() 方法调用先行发生于此线程的每一个动作(run中的操作)。
- 线程加入规则(Thread Join Rule),Thread 对象的结束先行发生于 join() 方法返回。
- 线程中断规则(Thread Interruption Rule),对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过 interrupted() 方法检测到是否有中断发生。
- 对象终结规则(Finalizer Rule),一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize() 方法的开始。
- 传递性(Transitivity),如果操作 A 先行发生于操作 B,操作 B 先行发生于操作 C,那么操作 A 先行发生于操作 C。
线程安全的实现方案?
- 互斥同步 synchronized 和 ReentrantLock。
- 非阻塞同步 cas
- 无同步方案
-
- 栈封闭
方法中没有成员变量 都是局部变量
- 栈封闭
-
- 线程本地存储
threadLocal
- 线程本地存储
-
- 可重入代码(Reentrant Code)
可重入代码有一些共同的特征,例如不依赖存储在堆上的数据和公用的系统资源、用到的状态量都由参数中传入、不调用非可重入的方法等。
- 可重入代码(Reentrant Code)
线程池的核心参数?
- coresize 核心线程数
- maxsize 最大线程数
- worker queue 工作队列
- 拒绝策略
- keepalive time 空闲时间大小
- keepalive timeunit 空闲时间单位
- threadfactory 线程工厂
线程池提交任务流程?
- 当前线程数小于核心线程,创建核心线程数执行任务
- 当前线程数大于核心线程,工作队列没满则将任务加入队列,满了的话,当前线程数小于最大线程数,则创建非核心线程执行任务,大于最大线程数则执行拒绝策略。
线程池的状态?
- running 【初始状态,在此状态下能够接收新任务,以及对已经添加的任务进行处理;】
- shutdown【通过调用shutdown方法,线程池转成SHUTDOWN状态。此时不再接收新任务,但是能处理已经添加的任务】
- stop【通过调用shutdownNow方法,线程池转成STOP状态。此时不再接收新任务,不处理已经添加的任务,并且会中断正在处理的任务;】
- tidying【当线程池中所有的任务已经终止了,任务数量为0并且阻塞队列为空的时候,会进入到TIDYING状态。此时会调用一个钩子方法terminated,它是一个空的实现,可以供调用者覆写;】
- teminated【线程池彻底终止的状态。当线程池处于TIDYING状态时,执行完terminated方法后,就会进入到该状态。】
jdk线程池的拒绝策略?
- CallRunsPolicy 由主线程执行
- AbortPolicy 抛出拒绝任务异常
- DiscardPolicy 拒绝任务,不抛出异常
To C端的接口,线程池的队列选择?核心线程和最大线程?
- 使用同步队列SynchronousQueue
- 使用线程池并发处理任务最大线程数和核心线程数,根据Runtime.getRunTime().availableProcessors()获取core数
- 决绝策略使用callerRunsPolicy
执行线程池的shutdown和shutdownnow的区别?
- shutdown 不再接受新的任务,正在执行的任务和队列中的任务正常执行
- shutdownnow 不在接受新的任务,正在执行任务打断,队列中的任务丢弃
有哪些方式可以控制线程的执行顺序?
- thread.join()
- reentrantLock + conditon(await+signal)
- countDownLanch
- cyclicBarrier
- wait() \notify\notifyAll 配合synconized关键字
- thread.park() + thread.unpark()