我正在阅读"Elixir in Action" book by Saša Jurić, and in the first chapter它说:
Erlang进程彼此完全隔离。他们分享 没有内存,一个进程崩溃不会导致其他进程崩溃 过程
对于Java线程也不是这样吗?我的意思是当Java线程崩溃时,它也不会崩溃其他线程 - 特别是,如果我们正在查看请求处理线程(让我们从这个讨论中排除main
线程)
答案 0 :(得分:86)
在我之后重复:"这些是不同的范例"
大声说出20次左右 - 这是我们目前的口头禅。
如果我们真的必须比较苹果和橘子,那么我们至少要考虑"果实和#34;的共同方面。相交。
Java"对象"是Java程序员的基本计算单位。也就是说,对象(基本上是一个带有包围somewhat more strictly enforced than in C++的手臂和腿的结构)是您用来模拟世界的主要工具。你认为"这个对象知道/有Data {X,Y,Z}
并对它执行Functions {A(),B(),C()}
,随身携带Data
,并且可以通过调用定义为的函数/方法与其他对象通信他们公共界面的一部分。它是一个名词,名词做的东西。"。也就是说,您可以围绕这些计算单位确定思维过程。默认情况是对象之间发生的事情按顺序发生,并且崩溃会中断该序列。它们被称为"对象"因此(如果我们无视Alan Kay的原始含义),我们会得到"面向对象"。
Erlang"进程"是Erlang程序员的基本计算单位。一个过程(基本上是一个在自己的时间和空间中运行的独立顺序程序)是Erlanger为世界建模的主要工具(1)。与Java对象如何定义封装级别类似,Erlang进程也定义了封装级别,但在Erlang的情况下,计算单位完全彼此切断。您不能在另一个进程上调用方法或函数,也不能访问其中的任何数据,也不能在与任何其他进程相同的时序上下文中运行,并且无法保证相关的消息接收顺序到可能正在发送消息的其他进程。他们也可能完全在不同的星球上(而且,想到它,这实际上是合理的)。它们可以彼此独立地崩溃,而其他过程只有在故意选择受到影响时才会受到影响(甚至这涉及到消息传递:基本上注册接收来自死亡过程的遗书本身并不能保证以任何形式到达相对于整个系统的顺序,您可能会或可能不会选择做出反应。)
Java直接在复合算法中处理复杂性:对象如何协同工作来解决问题。它旨在在单个执行上下文中执行此操作,Java中的默认情况是顺序执行。 Java中的多个线程表示多个运行的上下文,并且是一个非常复杂的主题,因为不同的时序上下文中的影响活动彼此之间(以及整个系统:因此防御性编程,异常方案等)。说"多线程"在Java 中意味着与Erlang不同的东西,实际上在Erlang中甚至都没有说过,因为它始终是基本情况。请注意,Java线程暗示与时间有关的隔离,而不是内存或可见引用 - 通过选择私有内容和公共内容来手动控制Java中的可见性;系统的普遍可访问元素必须设计为线程安全"线程安全"和可重入,通过排队机制顺序化,或采用锁定机制。简而言之:调度是线程/并发Java程序中的手动管理问题。
Erlang分离每个进程'在执行时间(调度),内存访问和参考可见性方面运行上下文,并通过将完全隔离它来简化算法的每个组件。这不仅仅是默认情况,这是此计算模型下唯一可用的情况。这样做的代价是,一旦处理序列的一部分穿过消息屏障,就永远不知道任何给定操作的顺序 - 因为消息本质上都是网络协议,并且没有方法调用可以保证在给定的内部执行上下文。这类似于为每个对象创建一个JVM实例,并且只允许它们跨套接字进行通信 - 这在Java中会非常麻烦,但是Erlang的设计方式是工作的(顺便说一句,这也是概念的基础)写作" Java微服务"如果一个人放弃了网络导向的行李,那么流行语往往会带来 - 默认情况下,Erlang程序是成群的微服务。这完全取决于权衡。
这些是不同的范例。我们可以找到的最接近的共性是,从程序员的角度来看,Erlang进程类似于Java对象。如果我们必须找到一些东西来比较Java线程......好吧,我们根本就不会在Erlang中找到类似的东西,因为在Erlang中没有这样的可比概念。击败死马:这些是不同的范例。如果你在Erlang中写一些非平凡的程序,这将很明显。
请注意,我说"这些是不同的范例"但是甚至没有涉及OOP与FP的话题。 "思考Java和#34;之间的区别和#34;思考Erlang"比OOP和FP更为基础。
虽然Erlang"并发导向"或者"面向过程的"基础更接近Alan Kay在创造术语"面向对象"(2)时的想法,这不是真正的重点。 Kay所得到的是,通过将你的同质体切割成离散的块,可以降低系统的认知复杂性,并且隔离是必要的。 Java以一种基本上仍然是程序性的方式实现了这一点,但是结构编码围绕一个特殊语法而不是高阶调度闭包,称为"类定义"。 Erlang通过按对象拆分运行上下文来实现此目的。这意味着Erlang的东西不能相互调用方法,但Java的东西可以。这意味着Erlang的东西可能会孤立地崩溃,但是Java的东西是不可能的。从这个基本差异中产生了大量的影响 - 因此"不同的范例"。权衡。
脚注:
答案 1 :(得分:16)
绝对不是。 Java中的所有线程共享相同的地址空间,因此一个线程可能会丢弃另一个线程拥有的内容。在Erlang VM中,这是不可能的,因为每个进程都与其他进程隔离。这就是他们的全部观点。只要您希望让一个进程对另一个进程执行某些操作,您的代码就必须向另一个进程发送消息。进程之间共享的唯一内容是大型二进制对象,这些对象是不可变的。
答案 2 :(得分:15)
Java进程实际上可以共享内存。例如,您可以将同一个实例向下传递给两个单独的线程,并且两者都可以操纵其状态,从而导致潜在问题,例如deadlocks。
另一方面,Elixir / Erlang通过不变性的概念来解决这个问题,所以当你将某些东西传递给一个过程时,它将是原始值的副本。答案 3 :(得分:4)
当Java线程死亡时,它也不会影响其他线程
让我问一个反问:为什么你认为Thread.stop()
被弃用了十多年?之所以恰恰是对上述陈述的否定。
举两个具体的例子:你stop()
一个帖子在执行某事时听起来像System.out.println()
或Math.random()
一样无害。结果:现在,整个JVM的这两个功能都已中断。这同样适用于您的应用程序可能执行的任何其他同步代码。
如果我们正在查看请求处理线程
应用程序可能在理论上被编码,以便绝对不会使用受锁保护的共享资源;但这只会有助于指出Java线程与代码相关的确切程度。并且"独立"实现只会涉及请求处理线程,而不属于此类应用程序中所有线程。
答案 4 :(得分:2)
为了补充以前的答案,Java线程有两种类型:守护进程和非守护进程。
要更改主题的类型,您可以拨打.setDaemon(boolean on)
。不同之处在于守护程序线程不会阻止JVM退出。正如线程的Javadoc所说:
当运行的唯一线程都是守护程序线程时,Java虚拟机退出。
这意味着:用户线程(未专门设置为守护进程的线程)使JVM无法终止。另一方面,当所有非守护程序线程完成时,守护程序线程可能正在运行,在这种情况下JVM将退出。所以,回答你的问题:你可以启动一个在完成后不退出JVM的线程。
至于与Erlang / Elixir的比较,请不要忘记:他们是不同的范例,如前所述。
JVM模仿Erlang的行为并非不可能,尽管它不是出于它的意图,因此,它需要进行大量的权衡。以下项目试图实现这一目标:
答案 5 :(得分:1)
对于Java线程也不是这样吗?我的意思是当Java线程崩溃时,它也不会崩溃其他线程
是和否。我解释:
引用共享内存:Java进程中的不同线程共享整个堆,因此线程可以以大量计划和非计划的方式进行交互。 然而堆栈中的对象(例如传递给被调用方法的上下文)或ThreadLocal
是他们自己的线程(除非他们开始共享引用)。
崩溃:如果一个线程在Java中崩溃(一个Throwable
传播到Thread.run()
,或某些东西被循环或阻塞),那个事故可能不会影响其他线程(例如服务器中的连接将继续运行)。 然而,因为不同的线程会互动。如果其中一个线程异常结束(例如,一个线程试图从另一个没有关闭其末端线程的空管道中读取),则其他线程很容易被搁浅。因此,除非开发人员高度谨慎,否则很可能会出现副作用。
我怀疑任何其他范例都打算将线程作为完全独立的岛屿运行。他们必须以某种方式分享信息和协调。然后就有机会搞砸了。只是他们会采取一种更具防御性的方法,“让你少挂绳子”(与指针一样的习语)。
答案 6 :(得分:0)
一开始,吸引力可能是这样的。在这样一个对比有限的上下文中(“一个撞另一个”),它们可能看起来是一样的。但是当我们涉足它们的细节和本质时,真正的区别就会浮出水面。 @zxq9 给出了相当多的对比细节,可能有助于理解它们确实在细节上有所不同。
-- 在分布式系统方面,ErLang 是一个工程奇迹。它对问题域的看法确实是惊人的,它对系统资源的处理方法确实与众不同。不会在其他任何地方遇到。