Stereotyped Writing(一)

线程的状态?

  1. 新建(NEW):线程被创建但并未启动
  2. 可运行(RUNNABLE):线程可以在任意时刻运行,也可能正在等待CPU分配时间片
  3. 堵塞(BLOCKED):线程被阻止执行,因为它正在等待监视器锁定,其他线程正在占用所需的锁定,因此线程被堵塞
  4. 等待(WAITING):线程进入等待状态,直到其他线程显示的唤醒他,线程可以调用Object类的wait()方法,join()方法或Lock类的条件等待方法进入此状态。
  5. 计时等待(INMD_WAITING):线程进入计时等待状态,等待一段指定时间,线程可以调用Thread.sleep()方法,Object的wait()方法,join()方法或Lock类的计时等待方法进入此状态。
  6. 终止(TERMINATED):线程完成了其任务,或者因为异常或其他原因而终止运行。

创建线程的方式

  1. 继承Thread类
  2. 实现Runnable接口
  3. 使用匿名内部类
  4. 使用Lambda表达式

线性池核心参数?

  • corePoolSize 线程池核心线程数量
  • maximumPoolSize 线程最大数量(包含核心线程数量)
  • keepAliveTime 当前线程池数量超过corePoolSize时,多余的空闲线程的存活时间,即多次时间内被销毁。
  • unit:keepAliveTime的单位
  • workQueue:线程池所使用的缓冲队列,被提交但尚未被执行的任务
  • threadFactory:线程工厂,用于创建线程,一般用默认的即可。
  • handler:拒绝策略,当任务太多来不及处理,如何拒绝任务

如何创建线程池?

不能使用Execytors直接创建线程池,会出现OOM为问题,要使用ThreadPoolExecutor构造方法创建。

  1. new ThredPoolExector方式
  2. spring的ThreadPoolTaskExecutor方式

线程池的工作原理?

线程池刚创建的时候,里面没有一个线程,任务队列是作为参数传进来的,不过,就算队列里面有任务,线程池也不会马上执行他们。

当调用execute()方法添加一个任务的时候,线程池会做如下判断

  • 如果正在运行的线程数量小于corePoolSize,那么马上船舰线程运行这个任务
  • 如果正在运行的线程大于或者等于corePoolSize,那么将这个任务放入队列。
  • 如果这时候队列满了,而且正在运行的线程数量小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务
  • 如果队列满了,而且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会抛出异常,RegectExecutionException

当一个线程完成任务时,它就会从队列中取下一个任务来执行

当一个线程无事可做,超过一定时间(keepAliveTime)时,线程池会判断,如果当亲运行的线程数大于corePoolSize,那么这个线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。

线程池的拒绝策略

  1. 默认:默认拒绝策略,直接抛出RejectedExecutionExeption异常
  2. 丢弃任务:直接丢弃无法处理的任务、
  3. 丢弃最早的任务:丢弃在线程池中最早的任务
  4. 抛出异常:抛出RegectedExecution异常
  5. 调用者运行:由提交任务的线程执行任务
  6. 使用调用者线程执行:使用提交任务的线程执行任务

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){//加锁
...
}//解锁
  1. 当执行到第一行的代码,会根据obj的对象头找到或创建的对象对应的Monitor对象
  2. 检查Monitor对象的owner属性,用Cas操作去设置owner为当前线程,Cas是原子操作,只能由一个线程能成功。
  3. 假设T0 Cas成功,那么T0就加锁成功,可以继续执行synchronized代码块内的部分。
  4. T1这边Cas失败,会自旋若干次,重新尝试加锁,如果重试过程中T0释放了锁,则T1不必堵塞,加锁成功;重试时T0仍持有锁,则T1会进入Monitor的等待队列堵塞,将来T0解锁后会唤醒它恢复运行(去重新抢锁)
posted @ 2024-03-08 20:08  奕帆卷卷  阅读(16)  评论(0)    收藏  举报