Scala并发编程实战:Executor线程池

限时加码!20+主流AI编程工具免费用 购周边加赠Coding Plan Lite,Claude Code、Cursor等即刻畅享,学习进阶更高效! 阅读详情

创建线程是一个重量级操作,因为需要调用操作系统内核的API,所以最好不要频繁的创建和销毁线程,为了能够复用创建的线程,常用的办法的就是创建线程池。

Executor

java.util.concurren包中提供了若干接口和类来实现线程池,最常用的有Executor,ExecutorService,ThreadPoolExecutor。

Executor接口很简单定义如下:

public interface Executor {
    void execute(Runnable command);
}

这个接口的目的在于将任务与执行机制解耦,使得用户不需要手动创建线程,只要交给Executor就行了。

ExecutorService

ExecutorService接口则扩展了Executor接口,增加了若干实用的方法,最常用的两个方法:

//关闭线程池
void shutdown();
//提交Callable任务以获取返回值
<T> Future<T> submit(Callable<T> task);

AbstractExecutorService抽象类是ExecutorService的实现,实现了若干模板方法。

最重要的类莫过于ThreadPoolExecutor,它是最最常用的ExecutorService实现类,下面重点说说。

ThreadPoolExecutor

ThreadPoolExecutor在构造时可以指定的参数最多有7个,另外还有3个使用一些默认参数的简化版本。

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
  • corePoolSize 是保留的核心线程数,即使线程处于空闲也不会被回收,除非设置了allowCoreThreadTimeOut属性。
  • maximumPoolSize 最大线程数。当workQueue满了,会给新提交的任务创建新线程,这种情况下线程数会超过corePoolSize,但整个线程池的线程数必须有个上限,就是maximumPoolSize了。
  • keepAliveTime 回收线程前,允许保留空闲线程的时长。
  • workQueue 存储提交的任务的队列
  • threadFactory 创建线程的工厂类(ThreadFactory这个接口就定义了一个方法Thread newThread(Runnable r);)
  • handler handler用于没有可用线程(线程数达到最大值,没有空闲线程)且workQueue队列满了的时候。

ThreadPoolExecutor 已经提供了以下 4 种策略。

CallerRunsPolicy:提交任务的线程自己去执行该任务。

AbortPolicy:默认的拒绝策略,会 throws RejectedExecutionException。

DiscardPolicy:直接丢弃任务,没有任何异常抛出。

DiscardOldestPolicy:丢弃最老的任务,其实就是把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列。

ThreadPoolExecutory的构造函数一共有四种,使得用户可以省略threadFactory和handler中的一个或两个。

需要注意的情况当maximumPoolSize>corePoolSize时,如果workQueue满了,新提交的任务会被新线程马上执行,而之前提交的在队列中等待的队列则继续等待。也就是说后提交的任务可能先执行了。当新线程执行完新提交的这个任务后,会转去执行队列中的数据,这时消费任务队列的线程数可能会大于corePoolSize,消费速度加快了。下面做个实验。

package io.github.liam8.con

import java.util.concurrent.{ArrayBlockingQueue, Callable, Future, RejectedExecutionException, ThreadPoolExecutor, TimeUnit}

object ExecutorDemo {

  def main(args: Array[String]): Unit = {
    // corePoolSize=1,maximumPoolSize=2,queue capacity=1
    val executor = new ThreadPoolExecutor(
      1,
      2,
      10,
      TimeUnit.SECONDS,
      new ArrayBlockingQueue[Runnable](1)
    )
    val task1 = new Runnable {
      override def run(): Unit = {
        println("task1 running")
        Thread.sleep(3000)
        println("task1 complete")
      }
    }
    val task2 = new Runnable {
      override def run(): Unit = {
        println("task2 running")
        Thread.sleep(3000)
        println("task2 complete")
      }
    }
    val task3 = new Callable[String] {
      override def call(): String = {
        println("task3 running")
        Thread.sleep(3000)
        println("task3 complete")
        "xxx"
      }
    }
    val task4 = new Runnable {
      override def run(): Unit = {
        println("task4 running")
        Thread.sleep(3000)
        println("task4 complete")
      }
    }
    var task2Result: Future[String] = null
    var taskCount = 1
    try {
      executor.execute(task1)
      println("task1 submitted")
      taskCount  = 1
      executor.execute(task2)
      println("task2 submitted")
      taskCount  = 1
      task2Result = executor.submit(task3)
      println("task3 submitted")
      taskCount  = 1
      executor.execute(task4)
      println("task4 submitted")
    } catch {
      case e: RejectedExecutionException => println(s"task $taskCount be rejected")
    }
    // 起一个线程跟踪线程池大小
    val th = new Thread {
      var threadNum = 0

      override def run(): Unit =
        while (true) {
          if (executor.getPoolSize != threadNum) {
            threadNum = executor.getPoolSize
            println("pool size:"   threadNum)
          }
          Thread.sleep(100)
        }
    }
    th.setDaemon(true)
    th.start()
    if (task2Result != null) {
      println(task2Result.get(7, TimeUnit.SECONDS))
    }
    Thread.sleep(5000)
    executor.shutdown()

  }


}

output

task1 running
task1 submitted
task2 submitted
task3 submitted
task3 running  //task3在task2之前运行了!
task 4 be rejected // 线程数达到最大值,任务队列也满了,task4被拒绝(默认的handler)
pool size:2
task1 complete
task3 complete
xxx
task2 running // 空闲的线程开始消费队列
task2 complete
pool size:0

Executors

Executors是JUC包中的一个静态工厂类,其中除了newFixedThreadPool,newSingleThreadExecutor方法,其他方法都不推荐使用,因为其他方法创建的线程池使用的是无界队列,可能会占用过多内存,甚至OOM,所以建议使用有界队列。

ExecutionContext

Scala另外提供了ExecutionContext和Future来简化线程池的使用,Future可以接受一个ExecutionContext类型的隐式参数,将传入的函数提交到ExecutionContext的线程池中运行。下面举个栗子,不做深入探讨。

package io.github.liam8.con

import java.util.concurrent.Executors

import scala.concurrent.{Await, ExecutionContext, ExecutionContextExecutorService, Future}
import scala.concurrent.duration._

object ExecutionContextDemo {

  def main(args: Array[String]): Unit = {
    val pool = Executors.newFixedThreadPool(2)
    implicit val ec: ExecutionContextExecutorService = ExecutionContext.fromExecutorService(pool)

    val f = Future {
      val t = Thread.currentThread().getName
      println(s"$t: future is coming")
      123
    }

    val re = f.map(r => {
      val t = Thread.currentThread().getName
      println(s"$t: mapping")
      r * r
    })
    re.onSuccess { case x: Int => println(x) }

    Await.result(f, 3.seconds)
    ec.shutdown()
  }

}

output

pool-1-thread-1: future is coming
pool-1-thread-2: mapping
15129

参考文献

Executor与线程池:如何创建正确的线程池?

Futures Made Easy with Scala

本文代码

Github仓库

转载请注明原文地址:https://liam-blog.ml/2019/09/22/Scala-Concurrency-Executor/

Scala语言的多线程编程 在现代软件开发中,随着多核处理器的普及,采用多线程编程的方式来提高程序运行效率已经成为一种趋势。Scala作为一种兼容Java的平台的多范式编程语言,具备了出色的并发编程能力。本文将深入探讨Scala语言的多线程编程,包括基础概念、常用库、最佳实践以及示例代码,帮助你更好地理解和使用Scala进行多线程开发。 阅读详情

相关推荐

控制Scala并行集合中线程池的线程数量

然而,有时候我们可能需要对线程池中的线程数量进行控制,以便更好地适应特定的场景和需求。首先,让我们通过一个简单的示例来说明如何使用Scala的并行集合。假设我们有一个包含100个元素的列表,我们要对列表中的每个元素进行平方运算。通过上述方式,我们可以灵活地控制并行集合中线程池的线程数量,以适应不同的计算需求。请注意,在设置线程数量时,应该根据系统的配置和计算任务的特性进行合理的选择,以充分利用系统资源并提高计算性能。希望本文能帮助你理解如何在Scala的并行集合中控制线程池的线程数量。

CodeLancerX的博客 274

Scala语法(五) Actor与线程池

前言 在前面的章节内, 我们介绍了Scala基础、集合、方法与函数体, 以及模式匹配的相关知识. 本章简单的介绍了Actor的相关知识. 正文 创建Actor 个人理解Actor对象与Java内的Thread对象相似. 创建时通过继承的方式创建Actor类, 并通过start()方法启动Actor子线程. import scala.actors.Actor /** * 使用Actor...

Be yourself. 1323

scala并行集合 控制线程池内线程个数

// _ooOoo_ // o8888888o // 88" . "88 // (| -_- |) // O\ = /O // ____/`---'\____ // ...

yy的博客 553

Scala 并发编程

Scala 并发编程 Runnable/Callable Runnable 接口只有一个没有返回值的方法。 trait Runnable { def run(): Unit } Callable与之类似,除了它有一个返回值 trait Callable[V] { def call(): V } 线程 Scala 并发是建立在 Java 并发模型基础上的。

赵英超的博客 1503

scala-sparkML学习笔记:Scala并发编程实战Executor线程池

原文地址:https://liam-blog.ml/2019/09/22/Scala-Concurrency-Executor/ 创建线程是一个重量级操作,因为需要调用操作系统内核的API,所以最好不要频繁的创建和销毁线程,为了能够复用创建的线程,常用的办法的就是创建线程池Executor java.util.concurren包中提供了若干接口和类来实现线程池,最常用的有Executo...

MachineLP的专栏 668

线程池Executor 框架

一 使用线程池的好处 线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。 使用线程池的好处: 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线...

weixin_42015465的博客 182

【构建并发程序】1-线程池-Executor-ExecutionContext

在每秒数千个请求的情况下,为每个请求都要创建一个新线程会拖慢系统的运行速度,因此同一个线程应该可以能够让许多个请求反复来进行使用;这些可重用的线程通常被称为线程池Executor是一个简单的接口,它定义了一个名为execute的方法,该方法接受Runnable对象,并且最终会调用该对象中的run方法。通常Executor接口会以并发的方式将Runnable对象,处理为调用execute方 法的线程,然后将这些线程实现为 “线程池”;

大白 441

Scala并发编程的传统构建块与自定义数据结构

本章深入探讨了Scala中并发程序的传统构建块,包括如何利用Executor对象运行并发计算、使用原子原语实现锁和无锁算法、以及懒惰值对并发程序的影响。同时,介绍了并发集合的重要类别,并探讨了如何在实际中应用它们。此外,本章还讲解了如何通过scala.sys.process包处理其他进程。这些知识不仅适用于Scala,许多其他语言和平台也有类似的并发工具。本章最后强调,尽管并发编程构建块更为高级,但仍需小心处理潜在问题,如避免死锁、饥饿或非确定性等问题。

weixin_28850145的博客 502

Executor源码分析

step1:CoarseGrainedExecutorBackend.scala /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this wor...

yangshaojun1992的博客 1198

Scala写Java线程池

import java.util.concurrent.{Callable, Executors, Future} /** * Created by 57871 on 2018/10/31. */ object java_Thread { def main(args: Array[String]): Unit = { val pool = Executors.ne...

eye2cro 1094

Scala入门学习之 【Java线程池

import java.util.concurrent.{Callable, Executor, Executors, Future} object ThreadDemo { def main(args: Array[String]) { val pool = Executors.newFixedThreadPool(5) for (i <- 1 to 10) {

freefish_yzx的博客 609

scala线程demo-newFixedThreadPool

object FixedThread {   def main(args: Array[String]): Unit = {     val threads = Executors.newFixedThreadPool(3);     for (i       threads.execute(new MyThread("fix-thread(" + i + ")"))    

cache007的专栏 1331

如何在Scala的for comprehension中使用Future

Scala语言中

azurelaker的博客 516

Java并发编程:Actor模型原理与实战应用

并发编程是现代软件开发的核心挑战之一,传统多线程模型面临共享内存和锁竞争等痛点。Actor模型作为一种替代方案,通过消息传递机制实现线程隔离,每个Actor维护独立状态并通过异步消息通信。这种设计不仅解决了线程安全问题,还显著提升了系统扩展性。在分布式系统和实时处理场景中,Actor模型展现出独特优势,如Akka框架在电商订单处理、物联网设备管理等领域的成功应用。通过消息队列和邮箱机制,开发者可以构建高并发、低延迟的系统架构,有效应对Java多线程编程中的常见瓶颈问题。

weixin_34306593的博客 326

线程池详解(通俗易懂超级好)

目标 【理解】线程池基本概念 【理解】线程池工作原理 【掌握】自定义线程池 【应用】java内置线程池 【应用】使用java内置线程池完成综合案例 线程池 线程池基础 线程池使用 线程池综合案例 4.学员练习 5.线程池总结 概念介绍 1:什么是线程池 2:为什么使用线程池 3:线程池有哪些优势 什么是池 什么是线程池 线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过的线程,这里的任务就是我们前面学过的实现了Runna.

拉格朗日的学习笔记 12万+

Requests-Scala与JDK 11 HttpClient:底层实现原理分析

Requests-Scala是一个Scala语言实现的HTTP客户端库,它移植自Python的Requests库,以灵活、直观且易于使用的特点著称。该库底层基于JDK 11引入的HttpClient API构建,通过封装和优化,为Scala开发者提供了更加友好的HTTP请求接口。本文将深入分析Requests-Scala如何与JDK 11 HttpClient协同工作,揭示其底层实现原理和性能优化

gitblog_00853的博客 974

Spark源码学习1.6——Executor.scala

Executor.scala 一、Executor类 首先判断本地性,获取slaves的host name(不是IP或者host: port),匹配运行环境为集群或者本地。如果不是本地执行,需要启动一个handler来监控所有的executor进程,避免阻塞。然后,初始化Spark执行环境。向SparkEnv注册executor资源,即registerSource方法。第三步,装载类,序列化类...

weixin_30786617的博客 110

scala学习 --------java实现并发编程(理解)

2.1 java中实现线程方式(2种) 使用继承Thread类或者实现Runable接口来编写线程,效率很低。 没有办法实现对线程有效的管理,造成资源浪费。 2.2 java并发编程Executors接口 2.2.1. Java通过Executors接口提供四种线程池 newFixedThreadPool (execute)创建一个定长的线程池。 //固定长度线程池 public...

Z_Data的博客 441

scala线程demo-callable

object CallableThread {   def main(args: Array[String]): Unit = {     val threads = Executors.newCachedThreadPool     val f1 = new FutureTask[String](new Callable[String] {       override def

cache007的专栏 881
上一篇: 基于Spark实现推荐算法-4:基于物品的协同过滤(实现篇)
下一篇: Scala implicit 隐式转换安全驾驶指南
liam08
博客等级 码龄10年 50粉丝 18原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值