线程的状态?
- 新建(NEW):线程被创建但并未启动
- 可运行(RUNNABLE):线程可以在任意时刻运行,也可能正在等待CPU分配时间片
- 堵塞(BLOCKED):线程被阻止执行,因为它正在等待监视器锁定,其他线程正在占用所需的锁定,因此线程被堵塞
- 等待(WAITING):线程进入等待状态,直到其他线程显示的唤醒他,线程可以调用Object类的wait()方法,join()方法或Lock类的条件等待方法进入此状态。
- 计时等待(INMD_WAITING):线程进入计时等待状态,等待一段指定时间,线程可以调用Thread.sleep()方法,Object的wait()方法,join()方法或Lock类的计时等待方法进入此状态。
- 终止(TERMINATED):线程完成了其任务,或者因为异常或其他原因而终止运行。
创建线程的方式
- 继承Thread类
- 实现Runnable接口
- 使用匿名内部类
- 使用Lambda表达式
线性池核心参数?
- corePoolSize 线程池核心线程数量
- maximumPoolSize 线程最大数量(包含核心线程数量)
- keepAliveTime 当前线程池数量超过corePoolSize时,多余的空闲线程的存活时间,即多次时间内被销毁。
- unit:keepAliveTime的单位
- workQueue:线程池所使用的缓冲队列,被提交但尚未被执行的任务
- threadFactory:线程工厂,用于创建线程,一般用默认的即可。
- handler:拒绝策略,当任务太多来不及处理,如何拒绝任务
如何创建线程池?
不能使用Execytors直接创建线程池,会出现OOM为问题,要使用ThreadPoolExecutor构造方法创建。
- new ThredPoolExector方式
- spring的ThreadPoolTaskExecutor方式
线程池的工作原理?
线程池刚创建的时候,里面没有一个线程,任务队列是作为参数传进来的,不过,就算队列里面有任务,线程池也不会马上执行他们。
当调用execute()方法添加一个任务的时候,线程池会做如下判断
- 如果正在运行的线程数量小于corePoolSize,那么马上船舰线程运行这个任务
- 如果正在运行的线程大于或者等于corePoolSize,那么将这个任务放入队列。
- 如果这时候队列满了,而且正在运行的线程数量小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务
- 如果队列满了,而且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会抛出异常,RegectExecutionException
当一个线程完成任务时,它就会从队列中取下一个任务来执行
当一个线程无事可做,超过一定时间(keepAliveTime)时,线程池会判断,如果当亲运行的线程数大于corePoolSize,那么这个线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。
线程池的拒绝策略
- 默认:默认拒绝策略,直接抛出RejectedExecutionExeption异常
- 丢弃任务:直接丢弃无法处理的任务、
- 丢弃最早的任务:丢弃在线程池中最早的任务
- 抛出异常:抛出RegectedExecution异常
- 调用者运行:由提交任务的线程执行任务
- 使用调用者线程执行:使用提交任务的线程执行任务
ThreadLocal的原理
- 什么是ThreadLocal
首先,它是一个数据结构,有点像HashMap,可以保存key:value键值对,但是一个ThredLocal只能保持一个,并且各个线程的数据互不干扰。每个线程对象内部有一个ThreadLocalMap,它用来存储这些需要线程隔离的资源
- 怎么区分他们
通过ThreadLocal,它作为ThreadLocalMap的key,而真正要线程隔离的资源作为ThreadLocalMap的value
ThreadLocal.set就是把ThreadLocal自己作为key,隔离资源作为值,存入当前线程的ThreadLocalMap
ThreadLocal.get就是把ThreadLocal自己作为key,到当前线程的ThreadLocalMap中去查找隔离资源
- ThreadLocal一定要记得用完之后调用remove()清空资源,避免内存泄漏
悲观锁和乐观锁
- 悲观锁
像是synchronized,Lock这些都属于悲观锁
如果发生了竞争,失败的线程会进入堵塞
为什么叫悲观锁?
害怕其他线程来同时修改共享资源,因此使用互斥锁来让同一时刻只能有一个线程来占用共享资源
- 乐观锁
像是AtomicInteger,AtomicReference等原子类,这些都属于乐观锁
如果发生了竞争,失败的线程不会堵塞,仍然会重试
为什么叫乐观锁?
不怕其他线程来同时修改共享资源,事实上它根本不加锁,所有线程都可以去修改共享资源,只不过并发时只有一个线程能成功,其他线程发现自己失败了,就要去重试,直至成功。
如果竞争少,能很快占用共享资源,适合使用乐观锁,如果竞争多,线程对共享的独占时间长,适合用悲观锁
synchronized原理
以重量级锁为例,比如t1和t2两个线程同时执行加锁代表,已经出现了竞争。
synchronized(obj){//加锁
...
}//解锁
- 当执行到第一行的代码,会根据obj的对象头找到或创建的对象对应的Monitor对象
- 检查Monitor对象的owner属性,用Cas操作去设置owner为当前线程,Cas是原子操作,只能由一个线程能成功。
- 假设T0 Cas成功,那么T0就加锁成功,可以继续执行synchronized代码块内的部分。
- T1这边Cas失败,会自旋若干次,重新尝试加锁,如果重试过程中T0释放了锁,则T1不必堵塞,加锁成功;重试时T0仍持有锁,则T1会进入Monitor的等待队列堵塞,将来T0解锁后会唤醒它恢复运行(去重新抢锁)