深色模式
Java 多线程相关知识
线程状态(6种)
NEW
RUNNABLE
READY
RUNNING
BLOCKED
WAITING
TIME_WAITING
TERMINATED
Java 锁
Monitor机制
每一个对象都有一个monitor,即监视器。
Monitor的机制如下图:
The Owner:同一时刻,只能有一个对象持有monitor。
Entry Set:当一个线程要获取monitor时,会首先进入entry-set排队,如果monitor没有被其它线程持有,这个线程会和wait-set中被唤醒的其它线程竞争monitor。
Wait Set:当一个线程持有monitor时,该monitor关联的对象调用了 wait()
方法,那么这个线程会释放monitor,并且进入wait-set中等待。然后,当该monitor关联的对象调用了 notify()
或 notifyAll()
方法后,wait-set中的线程被唤醒,被唤醒的线程将与entry-set中的线程竞争monitor。( notify()
方法会随机唤醒一个线程。)
死锁的4个条件
- 互斥条件:该资源任意一个时刻只由一个线程占用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
锁的分类
线程是否锁住同步资源?
- 锁住
- 悲观锁
- 认为自己在使用数据的时候一定有别的线程来修改数据。
synchronized
关键字和Lock
的实现类都是悲观锁。
- 悲观锁
- 不锁住
- 乐观锁
- 认为自己在使用数据时不会有别的线程修改数据。
- 无锁编程,CAS算法,Java原子类。
- 乐观锁
锁住同步资源失败,线程是否阻塞?
- 阻塞
- 不阻塞
- 自旋锁
- 避免线程切换的开销。
- 要占用处理器的时间。
- 适应性自旋锁
- 自动调节自旋等待时间。
- CAS算法,Java原子类。
- 自旋锁
多个线程竞争同步资源的流程细节区别?
- 不锁住资源,多个线程中只有一个能修改资源成功,其它线程会重试。
- 无锁
- 同一个线程执行同步资源时自动获取资源。
- 偏向锁
- 多个线程竞争同步资源时,没有获取资源的线程自旋等待锁释放。
- 轻量级锁
- 多个线程竞争同步资源时,没有获取资源的线程阻塞等待唤醒。
- 重量级锁
多个线程竞争锁时要不要排队?
- 排队
- 公平锁
- 先插队,插队失败再排队
- 非公平锁
一个线程中的多个流程能不能获取同一把锁?
- 能
- 可重入锁
- 不能
- 非可重入锁
多个线程能不能共享一把锁?
- 能
- 共享锁
- 不能
- 排他锁
线程调度相关方法
Object
wait()
:释放锁。notify()
:随机唤醒。notifyAll()
:唤醒所有。
Thread
sleep()
:不释放锁。线程暂停。yield()
:使用线程从RUNNING
回到RUNNABLE
状态,以使其它相同优先级的线程有运行的机会。join()
:等待子线程执行完。
启动线程的方式
Thread
Runnable
Callable
Java 线程池
ThreadPoolExecutor
类
构造方法参数
int corePoolSize
:核心池的大小,核心池在初始状态下,没有线程,当有任务过来时,会创建线程,直到达到corePoolSize个数。int maximumPoolSize
:线程数的上限,当任务队列满时,会额外创建临时线程,临时线程与核心线程的总数达到maximumPoolSize后,会采用拒绝策略。long keepAliveTime
:临时线程的空闲时长,超过corePoolSize的线程的idle时长,超过这个时间,多余的线程会被回收。TimeUnit unit
:时间的单位BlockingQueue<Runnable> workQueue
:任务队列,核心线程无空闲,则新任务进入队列中,队列满,则创建临时线程。ThreadFactory threadFactory
:新线程的产生方式RejectedExecutionHandler handler
:拒绝策略
线程池的任务处理策略
- 如果当前线程池中的线程数目小于
corePoolSize
,则每来一个任务,就会创建一个线程去执行这个任务; - 如果当前线程池中的线程数目
>=``corePoolSize
,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;如果当前线程池中的线程数目达到maximumPoolSize
,则会采取任务拒绝策略进行处理; - 如果线程池中的线程数量大于
corePoolSize
时,如果某线程空闲时间超过keepAliveTime
,线程将被终止,直至线程池中的线程数目不大于corePoolSize
;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime
,线程也会被终止。
总结:corePoolSize
-> 任务队列 -> maximumPoolSize
-> 拒绝策略
任务队列种类
- 直接提交队列
SynchronousQueue
:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。 - 无界任务队列
LinkedBlockingQueue
:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE
。 - 有界任务队列
ArrayBlockingQueue
:基于数组的先进先出队列,此队列创建时必须指定大小。
拒绝策略种类
AbortPolicy
:丢弃任务并抛出RejectedExecutionException
CallerRunsPolicy
:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。DiscardOldestPolicy
:丢弃队列中最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。DiscardPolicy
:丢弃任务,不做任何处理。
提交任务的方式
Future<T> submit(Callable<T> task)
:有返回结果void execute(Runnable command)
:无返回结果Future<?> submit(Runnable task)
:返回结果为null
线程池关闭
shutdown()
:不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务。shutdownNow()
:立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务。
Executors
类,常见的4种线程池
newFixedThreadPoll()
:固定大小线程池corePoolSize
和maximumPoolSize
相等- 队列使用的是
LinkedBlockingQueue
,默认为最大值
newSingleThreadExecutor()
:单一线程线程池corePoolSize
和maximumPoolSize
都是1- 队列使用的是
LinkedBlockingQueue
newCachedThreadPool()
:缓存线程池corePoolSize
是0,maximumPoolSize
是最大值- 超时时间默认60秒
- 队列使用的是
SynchronousQueue
newScheduledThreadPool()
:定时线程池- 该线程池可用于周期性地去执行任务,通常用于周期性的同步数据。
Java 定时任务
都可以定时执行、周期执行:
ScheduledThreadPoolExecutor
基于线程池。
Timer
单一的任务队列。