线程

本文介绍了Java中的线程概念,包括线程的优先级、守护线程以及创建线程的两种方式。接着详细讲解了线程池的原理和使用,如线程池的创建、核心线程数与最大线程数的设定、线程池的存活时间以及不同类型的线程池策略。同时,讨论了线程池中的任务队列选择、任务拒绝策略以及线程池的关闭和监控。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程

JVM允许一个应用有多个线程同时运行
线程有优先级,高优先级线程比低优先级线程优先被执行。线程可以标记成daemon。在线程里创建一个新的线程,这个新线程的初始优先级等于创建线程的优先级。当创建线程是daemon线程时,新线程就是daemon线程。
JVM启动时,通常有一个非daemon线程,有些地方成为main。JVM不停的执行线程,遇到下面情况之一停止:
1 Runtime的exit方法被调用了,并且安全管理允许exit操作发生。
2 所有的非daemon线程已经死了,即run执行结束或者在run抛出了异常。

有2种方式创建线程,一个是声明一个Thread类的子类,并且重写Thread类的run方法。然后子类的实例就可以被分配和启动。另一个是声明一个类实现Runnable接口,这类实现run方法。这个类的实例可以被分配出来,在创建线程的时候作为一个参数被传递,被启动。

线程都有一个作为身份的名称,多个线程可能有同样的名字,如果线程创建的时候没有指定名称,会自动生成一个新的名称。

在线程里的构造函数和方法里传空的参数会导致空指针异常。

线程池

用Executors的工厂方法配置线程池。
线程池解决2个问题:在执行大量异步任务时提供更好的性能,减少了任务前调用的开销;提供一种限制和管理线程的方式。
为方便使用,在Executors中封装了几个方便的工厂方法:newCachedThreadPool(无限制线程池,自动申请线程),newFixedThreadPool(固定线程池),newSingleThreadExecutor(单后台线程池)。这些预设置可以满足大部分的通用使用场景。

核心线程数和最大线程数
ThreadPoolExecutor根据核心线程数和最大线程数自动调整线程数。

当一个新的任务从方法execute被提交,如果当前线程数(运行中的线程)比核心线程数小,将创建一个新的线程处理请求,即使有其他的线程处于idle。如果当前线程数比核心线程数大,但比最大线程数小,只有当队列满了才会创建一个新的线程。通过设置核心线程数和最大线程数一样大,就创建了一个固定线程池。通过设置最大线程数无穷大,就允许线程池容纳任意数量的并发任务。核心线程数和最大线程数根据构造函数设置,之后可以通过setCorePoolSize和setMaximumPoolSize改变。

默认情况下,当新任务到来时新的线程被创建。可以通过重写prestartCoreThread或者prestartAllCoreThreads方法提前启动线程。

新的线程通过ThreadFactory被创建。默认使用DefaultThreadFactory,创建的所有线程都是同一个线程组,具有同样的优先级和daemon状态。提供一个不同的ThreadFactory可以提示线程的名称,线程组,优先级,daemon状态等等。如果ThreadFactory创建线程失败,返回null,ThreadPoolExecutor会继续,但可能无法执行任何任务。线程需要用有modifyThread的运行时权限。如果使用ThreadPoolExecutor的线程没有这个权限,服务能力就会降低,例如改变配置不能达到预期效果,关闭的线程池一直不能完成停止。

如果当前线程数比核心线程数大,过量的线程在经过存活时间的idle后会被停止。这样就提供了一种在线程池不活跃时减少资源消耗的方式,如果之后线程池变得活跃起来,新的线程将被创建。可以通过setKeepAliveTime动态设置存活时间。使用一个无穷大的值会阻止idle线程在线程池关闭前被停止。默认情况下,存活策略只应用于超过核心线程数的线程,但是allowCoreThreadTimeOut方法可用来应用到核心线程。

队列的使用和线程池大小有关系。
当前线程数比核心线程数小,新的线程被创建而不是入队。
当前线程数比核心线程数大,入队而不是创建新的线程。
无法入队时,如果当前线程数比最大线程数小,新的线程被创建;如果当前线程比最大线程数大,任务被拒绝。
入队有3种方案;
直接交付,可以使用SynchronousQueue,直接把任务交给线程,不持有这些任务。如果没有可用线程及时的运行任务,入队就会失败,因此一个新的线程被创建。这种策略可以在处理一系列有内部依赖关系的任务时防止被锁定,直接交付需要无限制的最大线程数防止任务被拒绝。因此,当命令到来比被执行快时,出现无限制线程的增长。
无限制队列,可以用LinkedBlockingQueue,没有预先定义的容量,当所有核心线程都在忙碌时,任务要在队列中等待。因此,除了核心线程,新的线程不会被创建,最大线程数就没有任何作用了。当任务是独立的,使用这个队列是合理的,任务互不影响的被执行。这个队列在缓解瞬时爆发的请求场景很有用。当命令到来比被执行快时,队列无限制增大。
有限队列,可以用ArrayBlockingQueue,有限队列可以在使用有限最大线程数时避免资源耗尽,但调度和控制更难。队列容量和最大线程池数可能会互相权衡。队列容量大而线程池小可以降低CPU使用、系统资源和上下文切换开销,但是会导致人为的低产。如果任务常常被阻塞,系统可能会为更多线程安排你不允许的时间。对容量小的队列使用一般需要更大的线程池,而这会让CPU保持忙碌,可能会遇到不能接受的调度开销,同样会降低产量。

任务通过execute提交后,当Executor关闭时会被拒绝,当Executor使用了有限的最大线程数,并且队列容量处于饱和也会被拒绝。任意一种情况execute调用RejectExecutionhandler的rejectExecution方法。有4种提前定义好的策略提供:
1 默认情况下ThreadPoolExecutor.AbortPolicy,抛出RejectExecutionException
2 ThreadPoolExecutor.CallerRunsPolicy,调用execute的线程自己运行这个任务。提供了一种简单的反馈控制机制,可以降低新任务提交的速度。
3 ThreadPoolExecutor.DiscardPolicy,简单丢掉任务
4 ThreadPoolExecutor.DisacrdOldestPolicy,如果Executor没有关闭,在队列头上的任务被丢掉,重新尝试执行execute,如果再一次失败,重复这个过程。
允许定义其他的RejectExecutionHandler类。

Hook方法
提供了可以重写的方法:beforExecute和afterExecute,在每个任务开始和结束的时候被调用。可以用这个方法操作执行的环境,例如ThreadLocals再初始化,收集统计事件,添加log入口。另外,重写terminated方法实现需要在Executor全部停止时完成的某一处理。

通过getQueue方法获取队列可以做一些调试和测试的事情, 不建议做其他的事情。

线程池在程序中不再被引用,没有剩余的线程,会被自动关闭。如果想要确认没有的引用的线程城池被回收了,必须安排没用的线程最终死掉,通过设置合理的存活时间,使用比零核心线程更低的限制或者设置allowCoreThreadTimeOut.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值