文章目录

  • 线程从创建到死亡的状态
  • 同步、异步
  • 线程同步方法
  • 线程池的种类、优势、运行流程
  • AQS
  • synchronized 和 java.util.concurrent.locks.Lock 的异同
  • 如何确保N个线程可以访问N个资源,但同时又不导致死锁?


线程从创建到死亡的状态
  1. 新建(new):新创建了一个线程对象。
  2. 可运行(runabble):线程对象创建后,其他线程(比如main线程)调用了该对象的start() 方法。
    该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu的使用权。
  3. 运行(runnning):可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。
  4. 阻塞(block):阻塞状态是指线程因为某种原因放弃了cpu的使用权,即让出了 cpu timeskice,暂时停止运行。直到线程进入可运行状态(runnable)状态,才有机会再次获取 cpu timeslice 转到 运行(running)状态。
  • 阻塞分三种情况
  1. 等待阻塞:运行(running)的线程执行了 object.wait()方法,JVM会吧该线程放入等待队列(waitting queue)中。
  2. 同步阻塞:运行(running)的线程在获取对象的同步锁是,若该同步锁被别的线程占用,则JVM会吧该线程放入锁池(lock pool)中。
  3. 其他阻塞:运行(running)的线程执行 Thread.sleep(long ms)或者Thread.join()方法,或者发出了 I/O 请求时,JVM会吧该想爱你成设置为阻塞状态。当 sleep() 超时时、join() 等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入可运行(runabble)状态。
  1. 死亡(dead):线程run(),main()方法执行结束,或者因为异常退出了 run() 方法,则该线程结束生命周期。死亡的想爱你成不可再次复生。
同步、异步
  • 同步:线程T1强依赖于线程T2,T1向T2发了一个请求,T1必须等到T2的回复才能继续自己的操作,若T2迟迟不响应,则T1就一直处于等待(阻塞状态)
  • 异步:与同步相反,T1向T2发送了一个请求,不管T2是否有响应,都不妨碍T1的正常运行。
线程同步方法
  • wait():使线程变成阻塞状态,释放锁。Object类方法,在对某对象使用了此方法之后,只有再对此对象使用notify() 或者 notifyall()方法,才能使此对象转为就绪状态,获得对象锁,即可进入运行状态
  • sleep():Thread类方法,暂时使线程转为阻塞状态,不过,不释放锁。导致线程暂停执行指定时间,把执行机会让给其他线程,监控状态依旧保持,到时间后会自动激活。
  • notify():随机使一个阻塞线程转为就绪状态,具体激活哪个线程,由JVM确定,不是按优先级确定
  • notifyall():激活所有阻塞线程,至于哪个线程可以运行,需要看他们争抢资源的能力了。
线程池的种类、优势、运行流程
  1. newCachedThreadPool:创建一个可缓存线程池,如果线程池长度(最大数量,Interger.MAX_VALUE)超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  2. newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在池队列中等待。
  3. newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。
  4. newSingleThreadExecutor::创建一个单线程化的线程池,他只会用唯一的工作线程来执行任务,保证所有任务按指定顺序(FIFO,LIFO,优先级)执行。并且在任意给定的时间不会有多个线程是活动的 。
  • 优势
  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  2. 提高响应速度。当任务达到时,任务可以不需要等到线程创建就能执行。
  3. 提高线程的可管理性,线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和和监控。
  • 运行流程
  • 线程池参数说明:
  1. corePoolSize(线程池的基本线程数)
  2. maximumPoolSize(线程池最大线程数)
  3. keepAliveTime(线程活动保持时间)
  4. TimeUnit(线程活动保持时间的单位)
  5. workQueue(任务队列)
  • ArrayBlockingQueue:基于数组结构的游街阻塞队列,FIFO排序
  • LinkedBlockingQueue:基于链表的阻塞队列,按FIFO对元素排序(吞吐量通常要高于ArrayBlockingQueue)。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
  • SynchronousQueue:不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
  • PriorityBlockingQueue:具有优先级的无线阻塞队列
  1. ThreadFactory:用于设置创建线程的工厂,可以设置名字
  2. RejectedExecutionHandler:(饱和策略)当队列和线程池都满了,说明线程池处于饱和状态,必须采用一种策略处理新任务时抛出异常
  • JDK1.5提供的四种策略
  1. AbortPolicy:直接抛出异常
  2. CallerRunsPolicy:只用调用者所在线程来运行任务
  3. DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务
  4. DiscardPolicy:不处理,直接丢弃。
  5. 当然也可以根据应用场景来实现 RejectedExecutionHandler 接口自定义策略。比如:记录日志或持久化不能处理的任务。
  • 当提交一个新任务到线程池时,线程池的处理流程如下:

[外链图片转存失败(img-sssACCEt-1565752889832)(images/ThreadPool.png)]

1. 首先线程池判断 核心线程池(corePoolSize)是否已满?
   - 没满:创建一个核心线程去处理任务
   - 满了:任务被放进任务队列workQueue排队等待执行
	2. 其次判断等待队列workQueue是都已满?
 - 没满:任务被放进任务队列workQueue排队等待执行
 - 满了:判断整个线程池的线程数是否达到了maximumPoolSize
	3. 判断整个线程池的线程数是否达到了maximumPoolSize?
 - 未达到:创建非核心线程执行任务
 - 达到了:根据饱和策略拒绝该任务。
AQS
  • 实现锁的框架
  • 内部实现的关键:FIFO队列,state状态。
  • 定义了内部类 ConditionObject
  • 拥有两种线程模式:独占模式和共享模式
synchronized 和 java.util.concurrent.locks.Lock 的异同
  • 同:Lock 可以完成 synchronized 的所有功能
  • 不同:Lock 有比 synchronized 更精确的线程语义和更好的性能。synchronized 后自动的释放锁,而 Lock是显式加锁,并且必须在finally从句中释放锁
如何确保N个线程可以访问N个资源,但同时又不导致死锁?
  • 一种简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定顺序获取锁。因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。
  • 死锁产生的四个条件:
  1. 互斥条件:一个资源每次只能被一个进程使用。
  2. 占有且等待:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不可强行占有:进程已获得的资源,在未使用完之前,不能强行剥夺。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。、
  • 这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,然而只要上述条件之一不满足,就不会发生死锁。
  • 处理死锁的基本方法:
  1. 死锁预防:通过设置某些限制条件,去破坏死锁的四个条件中的一个或几个条件,来预防发生死锁。(但由于所施加的限制条件往往太严格,因而导致系统资源利用率和系统吞吐量降低)
  2. 死锁避免:允许前三个必要条件,但通过一定的选择,确保永远不会到达死锁点,因此死锁避免比死锁预防允许更多的并发。
  3. 死锁检测:不采用任何限制性措施,允许系统在运行过程中发生死锁,但可通过设置的检测机构及时检测死锁的发生,并精确的确定死锁相关的进程和资源,然后采取是适当的措施,从系统中将已发生的死锁清除掉。
  4. 死锁解除:与死锁检测配套的一种措施。当检测到系统中已发生死锁,需将进程从思索状态中解脱出来。
  • 常用方法:撤销或挂起一些进程,以便回收一些资源,再将这些资源分配给已处于阻塞状态的进程。死锁检测和解除有可能使系统获得较好的资源利用率和吞吐率,但在实现上难度也最大。
  • **死锁预防:**破坏死锁的四个条件中的一个或几个
  1. 互斥:它是设备固有的属性所决定的,不仅不能改变,还应该加以保证。
  2. 占有且等待:为预防占有且等待条件,可以要求进程一次性的请求所有需要的资源,并且阻塞这个进程直到所有请求都同时满足。这个方法比较低效。
  3. 不可强行占有:预防方法:
  • 如果一个进程请求当前被另一个进程占有的一个资源,则操作系统可以抢占另外一个进程,要求它释放资源。
  • 如果占有某些资源的一个进程进行进一步的资源请求被拒绝是,则该进程必须释放它最初占有的资源。
  1. 循环等待:通过定义资源类型的线性顺序来访问。如果一个进程已经分配了R类资源,那么接下来请求的资源只能是那些排在R类型之后的资源类型。该方法比较低效。
  • **死锁避免:**允许前三个条件,
  • 两种死锁避免算法:
  1. 进程启动拒绝:如果一个进程的请求会导致死锁,则不启动该进程。
  2. 资源分配拒绝:如果一个进程增加的资源请求会导致死锁,则不允许此分配(银行家算法)
  • 死锁检测和解除
  • 死锁解除算法:剥夺资源和撤销进程。