ExecutorService线程容量疑问及消息应用线程池实现方案咨询
消息应用线程池与客户端处理问题答疑
先解决你的核心疑问
咱们先聊你最关心的两个问题:
Executors.newFixedThreadPool(x)能不能处理无限多的Runnable?
答案是:理论上可以(只要内存够撑住任务队列)。FixedThreadPool会维护x个固定的核心线程,当你提交的任务超过线程数时,多余的任务会被放到一个无界阻塞队列里排队,等有空闲线程了再依次执行。不过要注意,如果任务太多导致队列过大,可能会吃掉大量内存,甚至出现OOM(内存溢出),这点要留意。- 这些Runnable是不是独立线程?
每个被执行的Runnable,确实是在线程池里的独立线程中运行的。线程池里的x个线程会轮流处理队列中的任务,每个任务都由其中一个线程执行,任务之间是并发的,并发上限就是你设置的x值。
另外看你的代码,目前只是把读取到的Client加入列表,还没调用clientPool.execute(current)把它提交到线程池执行哦,这部分得补上才能让Client的逻辑跑起来~
现有代码的优化点
你的当前实现有几个可以调整的地方,帮你更稳地跑起来:
- 补全Client的Runnable实现:你说Client类实现Runnable,但代码里它只实现了Serializable,得先让Client实现Runnable接口,写出
run()方法,这样线程池才能执行它的业务逻辑。 - 线程安全问题:
LinkedList<Client>和LinkedList<Message>都不是线程安全的,当多个客户端线程或者Server线程同时操作这些列表时,很容易出并发问题。建议换成CopyOnWriteArrayList,或者用Collections.synchronizedList(new LinkedList<>())包装一下,或者自己加锁保护操作。 - 资源别漏了:在Server的accept循环里,处理客户端连接时,要记得在客户端断开后关闭Socket、输入输出流,不然容易造成资源泄漏。
- 线程池要优雅关闭:当服务器要停止时,记得调用
clientPool.shutdown()或者shutdownNow(),不然线程池里的线程会一直挂着,没法正常退出。
给你几个替代实现方案
如果FixedThreadPool不符合你的场景,还有其他选择:
- CachedThreadPool:用
Executors.newCachedThreadPool(),它会根据任务数量动态创建线程,空闲线程会在60秒后自动回收。适合短任务多、并发波动大的场景,但要注意如果任务爆发式增长,可能会创建大量线程,把系统资源占满,这点要权衡。 - 自定义ThreadPoolExecutor:要是需要更精细的控制(比如限制队列大小、设置任务满了之后的处理策略),直接用
ThreadPoolExecutor的构造方法就行,比如:
ExecutorService clientPool = new ThreadPoolExecutor( 3, // 核心线程数 10, // 最大能创建的线程数 60L, TimeUnit.SECONDS, // 空闲线程存活时间 new ArrayBlockingQueue<>(100), // 最多存100个等待任务的有界队列 new ThreadPoolExecutor.AbortPolicy() // 队列满了就拒绝新任务,抛出异常 );
这样能避免无界队列导致的内存溢出问题,更可控。
- 不使用线程池(不推荐):直接给每个客户端创建新线程
new Thread(current).start(),但客户端多了之后会创建成千上万个线程,系统根本扛不住,除非你确定客户端数量极少,不然别用这个方案。
另外,你在main里启动MessageServer线程的方式是没问题的,不过注意你给ServerSocket设置了超时,accept方法会定时抛出SocketTimeoutException,你的catch块虽然处理了,但可以区分一下异常类型,比如超时异常不用打SEVERE级别的日志,不然日志里会一堆没用的报错,看着闹心~
内容的提问来源于stack exchange,提问作者guacamoleku
相关产品推荐
相关产品推荐

