- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
若无法通过并行流实现并发,则必须创建并运行自己的任务。运行任务的理想Java 8方法就是CompletableFuture。
Java并发的历史始于非常原始和有问题的机制,并且充满各种尝试的优化。本文将展示一个规范形式,表示创建和运行任务的最简单,最好的方法。
Java初期通过直接创建自己的Thread对象来使用线程,甚至子类化来创建特定“任务线程”对象。手动调用构造函数并自己启动线程。创建所有这些线程的开销变得非常重要,现在不鼓励。Java 5中,添加了类来为你处理线程池。可以将任务创建为单独的类型,然后将其交给ExecutorService运行,而不是为每种不同类型的任务创建新的Thread子类型。ExecutorService为你管理线程,并在运行任务后重新循环线程而不是丢弃线程。
这只是个包含run()方法的Runnable类。它没有包含实际运行任务的机制。使用Nap类中的“sleep”:
第二个构造函数在超时的时候,会显示一条消息。TimeUnit.MILLISECONDS.sleep():获取“当前线程”并在参数中将其置于休眠状态,这意味着该线程被挂起。这并不意味着底层处理器停止。os将其切换到其他任务,例如在你的计算机上运行另一个窗口。OS任务管理器定期检查**sleep()**是否超时。当它执行时,线程被“唤醒”并给予更多处理时间。
sleep()抛已检查的InterruptedException:通过突然中断它们来终止任务。由于它往往会产生不稳定状态,所以不鼓励用来终止。但我们必须在需要或仍发生终止的情况下捕获该异常。
结果:
All tasks submitted
main awaiting termination
main awaiting termination
NapTask[0] pool-1-thread-1
main awaiting termination
NapTask[1] pool-1-thread-1
main awaiting termination
NapTask[2] pool-1-thread-1
main awaiting termination
NapTask[3] pool-1-thread-1
main awaiting termination
NapTask[4] pool-1-thread-1
main awaiting termination
NapTask[5] pool-1-thread-1
main awaiting termination
NapTask[6] pool-1-thread-1
main awaiting termination
NapTask[7] pool-1-thread-1
main awaiting termination
NapTask[8] pool-1-thread-1
main awaiting termination
NapTask[9] pool-1-thread-1
创建十个NapTasks并将它们提交给ExecutorService,它们开始自己运行。然而,期间main()继续运行。当运行至exec.shutdown();
时,main告诉ExecutorService完成已提交的任务,但不再接受新任务。此时,这些任务仍在运行,必须等到它们在退出main()之前完成。这是通过检查exec.isTerminated()
来实现:在所有任务完成后为true。
main()中线程的名称是main,且只有一个其他线程pool-1-thread-1。此外,交错输出显示两个线程确实在同时运行。
若仅调用exec.shutdown()
,程序将完成所有任务,若尝试提交新任务将抛RejectedExecutionException。
exec.shutdown()的替代方法exec.shutdownNow():除了不接受新任务,还会尝试通过中断任务来停止任何当前正在运行的任务。同样,中断是错误的,容易出错,不鼓励!
使用线程的重点几乎总是更快地完成任务,那为何要限制自己使用SingleThreadExecutor?Executors还给了我们更多选项,如CachedThreadPool:
运行该程序时,你会发现它完成得更快。这是有道理的,而不是使用相同线程来顺序运行每个任务,每个任务都有自己的线程,所以它们并行运行。似乎没有缺点,很难看出为什么有人会使用SingleThreadExecutor。
要理解这个问题,需要一个更复杂任务:
用CachedThreadPool试一下:
输出结果:
0 pool-1-thread-1 195
3 pool-1-thread-4 400
2 pool-1-thread-3 300
1 pool-1-thread-2 200
5 pool-1-thread-6 600
6 pool-1-thread-7 700
4 pool-1-thread-5 500
7 pool-1-thread-3 800
8 pool-1-thread-5 900
9 pool-1-thread-7 1000
输出不是期望的,并且从一次运行到下一次运行会有所不同。问题是所有的任务都试图写入val的单个实例,并且他们正在踩着彼此的脚趾。这样的类就不是线程安全的。
看SingleThreadExecutor表现怎样:
输出结果:
0 pool-1-thread-1 100
1 pool-1-thread-1 200
2 pool-1-thread-1 300
3 pool-1-thread-1 400
4 pool-1-thread-1 500
5 pool-1-thread-1 600
6 pool-1-thread-1 700
7 pool-1-thread-1 800
8 pool-1-thread-1 900
9 pool-1-thread-1 1000
每次都得到一致结果,虽然InterferingTask缺乏线程安全性。这是SingleThreadExecutor的主要好处 - 因为它一次运行一个任务,这些任务不会相互干扰,等于强加了线程安全性。这种现象称为线程限制,因为在单线程上运行任务限制了它们的影响。【线程限制】限制了加速,但能节省很多困难的调试和重写。
因为InterferingTask是Runnable,无返回值,因此只能使用副作用产生结果 - 操纵缓冲值而不是返回结果。副作用是并发编程中的主要问题之一,因为我们看到了CachedThreadPool2.java。InterferingTask中的val被称为可变共享状态,这就是问题:多个任务同时修改同一个变量会产生竞争。结果取决于首先在终点线上执行哪个任务,并修改变量(以及其他可能性的各种变化)。
避免竞争条件的最好方法是避免可变的共享状态,可称为自私的孩子原则:什么都不分享。
使用InterferingTask,最好删除副作用并返回任务结果。为此,我们创建Callable而非Runnable:
call()完全独立于所有其他CountingTasks生成其结果,这意味着没有可变的共享状态。
输出结果:
0 pool-1-thread-1 100
2 pool-1-thread-3 100
1 pool-1-thread-2 100
3 pool-1-thread-4 100
4 pool-1-thread-5 100
5 pool-1-thread-6 100
6 pool-1-thread-7 100
7 pool-1-thread-5 100
8 pool-1-thread-7 100
9 pool-1-thread-6 100
sum = 1000
所有任务完成后,invokeAll()才会返回一个Future列表,每个任务一个Future。Future是Java 5中引入的机制,允许提交任务而无需等待它完成。
结果:
99 pool-1-thread-1 100
100
但这意味着,在CachedThreadPool3.java中,Future似乎是多余的,因为**invokeAll()**在所有任务完成前都不会返回。但此处的Future并非用于延迟结果,而是捕获任何可能的异常。
在CachedThreadPool3.java.get()抛异常,因此extractResult()在Stream中执行此提取。因为调用get()时,Future会阻塞,所以它只能解决【等待任务完成】的问题。最终,Futures被认为是一种无效解决方案,现在不鼓励,支持Java 8的CompletableFuture,将在后面探讨。当然,你仍会在遗留库中遇到Futures。
可使用并行Stream,更简单优雅解决该问题:
输出结果:
4 ForkJoinPool.commonPool-worker-15 100
1 ForkJoinPool.commonPool-worker-11 100
5 ForkJoinPool.commonPool-worker-1 100
2 ForkJoinPool.commonPool-worker-9 100
0 ForkJoinPool.commonPool-worker-6 100
3 ForkJoinPool.commonPool-worker-8 100
9 ForkJoinPool.commonPool-worker-13 100
6 main 100
8 ForkJoinPool.commonPool-worker-2 100
7 ForkJoinPool.commonPool-worker-4 100
1000
这更容易理解,需要做的就是将**parallel()**插入到其他顺序操作中,然后一切都在同时运行。
使用lambdas和方法引用,你不仅限于使用Runnables和Callables。因为Java 8通过匹配签名来支持lambda和方法引用(即支持结构一致性),所以我们可以将不是Runnables或Callables的参数传递给ExecutorService:
输出结果:
Lambda1
NotRunnable
Lambda2
NotCallable
这里,前两个submit()调用可以改为调用execute()。所有submit()调用都返回Futures,你可以在后两次调用的情况下提取结果。
Task.WaitAll 方法等待所有任务,Task.WaitAny 方法等待一个任务。如何等待任意N个任务? 用例:下载搜索结果页面,每个结果都需要一个单独的任务来下载和处理。如果我使用 WaitA
我正在查看一些像这样的遗留 C# 代码: await Task.Run(() => { _logger.LogException(LogLevel.Error, mes
如何在 Linux 中运行 cron 任务? 关注此Q&A ,我有这个 cron 任务要运行 - 只是将一些信息写入 txt 文件, // /var/www/cron.php $myfile = fo
原谅我的新手问题,但我想按顺序执行三个任务并在剧本中使用两个角色: 任务 角色 任务 角色 任务 这是我到目前为止(任务,角色,任务): --- - name: Task Role Task ho
我有一个依赖于 installDist 的自定义任务 - 不仅用于执行,还依赖于 installDist 输出: project.task('run', type: JavaExec, depends
从使用 Wix 创建的 MSI 运行卸载时,我需要在尝试删除任何文件之前强行终止在后台运行的进程。主要应用程序由一个托盘图标组成,它反射(reflect)了 bg 进程监控本地 Windows 服务的
我想编写 Ant 任务来自动执行启动服务器的任务,然后使用我的应用程序的 URL 打开 Internet Explorer。 显然我必须执行 startServer先任务,然后 startApplic
使用 ASP.NET 4.5,我正在尝试使用新的 async/await 玩具。我有一个 IDataReader 实现类,它包装了一个特定于供应商的阅读器(如 SqlDatareader)。我有一个简
使用命令 gradle tasks可以得到一份所有可用任务的报告。有什么方法可以向此命令添加参数并按任务组过滤任务。 我想发出类似 gradle tasks group:Demo 的命令筛选所有任务并
除了sshexec,还有什么办法吗?任务要做到这一点?我知道您可以使用 scp 复制文件任务。但是,我需要执行其他操作,例如检查是否存在某些文件夹,然后将其删除。我想使用类似 condition 的东
假设我有字符串 - "D:\ApEx_Schema\Functions\new.sql@@\main\ONEVIEW_Integration\3" 我需要将以下内容提取到 diff 变量中 - 文档名
我需要编写一个 ant 任务来确定某个文件是否是只读的,如果是,则失败。我想避免使用自定义选择器来为我们的构建系统的性质做这件事。任何人都有任何想法如何去做?我正在使用 ant 1.8 + ant-c
这是一个相当普遍的计算机科学问题,并不特定于任何操作系统或框架。 因此,我对与在线程池上切换任务相关的开销感到有些困惑。在许多情况下,给每个作业分配自己的特定线程是没有意义的(我们不想创建太多硬件线程
我正在使用以下 Ansible playbook 一次性关闭远程 Ubuntu 主机列表: - hosts: my_hosts become: yes remote_user: my_user
如何更改 Ant 中的当前工作目录? Ant documentation没有 任务,在我看来,最好的做法是不要更改当前工作目录。 但让我们假设我们仍然想这样做——你会如何做到这一点?谢谢! 最佳答案
是否可以运行 cronjob每三天一次?或者也许每月 10 次。 最佳答案 每三天运行一次 - 或更短时间在月底运行一次。 (如果上个月有 31 天,它将连续运行 2 天。) 0 0 */3 * *
如何在 Gradle 任务中执行托管在存储库中的工具? 在我的具体情况下,我正在使用 Gradle 构建一个 Android 应用程序。我添加了一项任务,将一些 protobuf 数据从文本编码为二进
我的项目有下一个结构: Root |- A |- C (depends on A) \- B (depends on A) 对于所有子项目,我们使用自己的插件生成资源:https://githu
我设置了一个具有4个节点的Hadoop群集,其中一个充当HDFS的NameNode以及Yarn主节点。该节点也是最强大的。 现在,我分发了2个文本文件,一个在node01(名称节点)上,一个在node
在 TFS 2010 中为多个用户存储任务的最佳方式是什么?我只能为一项任务分配一个。 (例如:当我计划向所有开发人员演示时) (这是一个 Scrum Msf 敏捷项目,其中任务是用户故事的一部分)
我是一名优秀的程序员,十分优秀!