创建多线程的方式:
run方法和start方法:
- 调用run方法和调用普通方法一样,是现有线程去同步串行地调用,可以调用多次
- 调用start方法是异步开启一个新线程,只能调用一次
线程的六个状态
wait()和slepp()的异同
重点:
- wait方法要与synchronized锁配合使用,sleep方法不需要
- wait(Long time)方法执行之后主动释放锁,给其他线程执行。sleep不会主动释放,只有sleep时间过了之后才释放
synchronized关键字原理
AQS原理
ReentrantLock(基于AQS实现)
死锁排查
jps命令:输出JVM的进程信息
jstack命令:查看java进程内线程的堆栈信息
ConcurrentHashMap
JDK 8 之前
一个 ConcurrentHashMap
里包含一个 Segment
数组,Segment
的个数一旦初始化就不能改变。 Segment
数组的大小默认是 16,也就是说默认可以同时支持 16 个线程并发写。
Segment
的结构和 HashMap
类似,是一种数组和链表结构,一个 Segment
包含一个 HashEntry
数组,每个 HashEntry
是一个链表结构的元素,每个 Segment
守护着一个 HashEntry
数组里的元素,当对 HashEntry
数组的数据进行修改时,必须首先获得对应的 Segment
的锁。也就是说,对同一 Segment
的并发写入会被阻塞,不同 Segment
的写入是可以并发执行的。
JDK 8 之后
ConcurrentHashMap
取消了 Segment
分段锁,采用 Node + CAS + synchronized
来保证并发安全。数据结构跟 HashMap
1.8 的结构类似,数组+链表/红黑二叉树。Java 8 在链表长度超过一定阈值(8)时将链表(寻址时间复杂度为 O(N))转换为红黑树(寻址时间复杂度为 O(log(N)))。
Java 8 中,锁粒度更细,synchronized
只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发,就不会影响其他 Node 的读写,效率大幅提升。
添加新节点使用CAS判断,如果哈希冲突了,用synchronized锁住链表/红黑树的首节点。
相对segment,锁的粒度更细,性能也就更好
volatile关键字作用
线程池
线程池7大参数
执行流程、拒绝策略
ThreadPoolExecutor.AbortPolicy
:抛出RejectedExecutionException
来拒绝新任务的处理。ThreadPoolExecutor.CallerRunsPolicy
:调用执行者自己的线程运行任务,也就是直接在调用execute
方法的线程中运行(run
)被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。ThreadPoolExecutor.DiscardPolicy
:不处理新任务,直接丢弃掉。ThreadPoolExecutor.DiscardOldestPolicy
:此策略将丢弃最早的未处理的任务请求。