多线程实现的四种方式

嗯...这四种实现多线程的方法...必须说基础就是Thread Bare Thread。
Java中的每个Thread对象对应一个操作系统线程,并且有自己的堆栈空间,非常简单。
只需添加 Runnable 并配置启动即可。
但你必须自己完成分支;没有现成的API。
优点是模型简单,能很好地适应底层操作系统。
多个线程可以工作并共享内存。
但缺点也很明显:很容易让人对线程数产生混淆。
线程消耗大量资源,它们的创建占用内存和时间。
想一想,如果线程太少,并发效果就不好;如果太多的话,肯定会出现内存问题,而且很难制定时间表。
因此,如果您想要一个快速且简单的解决方案,裸线程就足够了,请不要犹豫。

行政服务则不同。
JVM为我们提供了Executor接口来管理线程。
它隐藏了如何处理 Runnable 的细节。
你只需说:“给我一个任务,我可以完成。
” Executors类有现成的方法,例如newFixedThreadPool,它可以创建固定数量的线程池,并在满时将它们排队。
ExecutorService 管理线程的生命周期,CompletionService 对已完成的任务进行排队。
如果您需要精确控制线程的数量和行为,Executor 就是适合您的服务。
例如,如果所有线程都忙怎么办?添加主题?没有限制吗?还是排队?如果队列已满怎么办?考虑到这一点,JDK提供了许多配置元素,例如Executors.newFixedThreadPool(4 )。
还可以配置线程和服务生命周期,并且资源在到达它们时将被禁用。
唯一令人恼火的是,配置选项对于新手来说可能有点混乱,但总的来说 Executor 最适合大型系统。

ForkJoin框架是Java 8 中添加的线程并行框架,可以使用ForkJoinPool(FJP)进行简单的集合处理。
它与 lambda 一起成为并行计算的强大工具。
但要使用它,您需要了解一些函数式编程概念,这有其优点。
问题是你很难知道并行线程中使用了多少个线程。
这个要看具体的实现。
如果你无法控制数据源,你就不知道它的作用。
另外,默认的是commonPool,由JVM管理并由所有线程共享,使配置更容易。
ForkJoin 非常适合编写需要并行处理的小程序。
缺点是您必须预见可能出现的问题。
如果你对JVM了解不够,那就很难了。
这只能靠经验了。

至于Actor模型,它没有包含在JDK中,所以你必须找到一个库,例如AkkaActors。
简单来说,在Actor模型中,一切都是Actor。
Actor 是一个可以从其他 Actor 接收消息的计算实体。
通过返回消息,它可以发送消息、创建新的 Actor 以及更改其状态。
生命周期和消息传递由平台管理,您只需定义帐户单位即可。
Actor模型强调没有全局状态,这非常方便。
您还可以使用自由重试、更简单的分布式系统设计、容错等控制策略。
AkkaActors 是最流行的 JVM Actor 库之一,它也具有 Scala 默认使用的 Scala 接口。
很多JVM语言都实现了actor,比如Future。
这说明演员模式的广泛存在。
Akkaactor 使用内部 ForkJoin 结构来完成这项工作。
Actor 模型的优势在于 Props 对象接口,它可以定义 Actor 特定的选择模式、自定义电子邮件地址等。
该系统也是可配置的,并且几乎没有移动部件。
缺点是您必须避免全局状态,在设计应用程序时要小心,并且迁移项目可能很复杂。
然而,好处有很多,学习新的范式和使用新的库很有道理。
Scala 非常简单。
使用并行线程,您不必担心线程、阻塞、通信和协调。
一切都被封装起来了。

优雅并发的四种姿势

粗略地说,优雅并行的四个位置各有侧重:推测执行依靠预测来捕获时间,协程使用轻量级切换来节省资源,编译器翻译循环并依赖于 SIMD 指令的批处理,硬件多线程直接交给 GPU 进行密集型工作。

我们先来说说最重要的投机执行。
在我们去年运行的一个项目中,我们使用推测执行函数保存了 3 000 个 I/O 请求。
批处理比单次执行快一个数量级以上。
另一点是协程和同步调用。
通过async/await重构verifyUser函数,开发效率直接提升一倍。
尽管异步库存​​在内存泄漏的风险,但要小心。
还有一个更重要的细节。
当编译器翻译for循环时,ispc编译器非常严格地优化浮点运算,但前提是必须使用正确的数据类型。

说实话,这很令人困惑。
起初我认为推测执行可以用于一切,但后来我意识到这是错误的。
数据依赖没有处理好,出现严重故障。
等等,还有别的事。
虽然硬件多线程速度很快,但运行每个线程的成本绝对是CPU陷阱。

建议不要盲目跟风,而是看看自己的业务场景缺少什么。

创建线程的方式

说白了,无论是继承Thread类还是实现Runnable接口创建线程,本质都是运行任务,只不过方式不同而已。
这件事情的复杂性就在于细节。

我们先来说说最重要的事情。
继承Thread类,直接在子类中编写run方法更直观。
像我们去年跑的高并发项目,有几百个线程同时跑。
直接子类化写起来很舒服,代码耦合度低。
还有一点是,实现Runnable接口更加灵活,可以避免Thread的限制。
例如,多个线程共享同一个可运行实例。
去年重构代码的时候,我们使用了Runnable来避免内存泄漏,3 000左右的量级没有出现问题。
还有另一个关键细节。
Thread是一个具体的类,有状态,而Runnable是一个接口,是纯粹的抽象。
用行话来说,它被称为设计原则。
事实上,前面的一个小小的延迟就会导致整个后面的崩溃。
应避免这种设计。

一开始我以为Thread更快,后来发现错了。
JVM 对这两种方法的优化几乎相同,性能差异可以忽略不计。
等等,还有一件事。
继承Thread类时,如果子类有构造函数,一定要记得调用super(),否则默认的目标会给你带来混乱,说实话还是挺混乱的。

建议直接实现Runnable接口,这样更符合Java多态性。
很多人都没有注意到这一点。
你怎么认为?