Erlang进程与Java线程
我正在阅读SašaJurić的“Elixir in Action”一书,第一章说:
Erlang进程完全相互隔离。 它们不共享内存,一个进程崩溃不会导致其他进程崩溃。
Java线程也不是这样吗? 我的意思是,当Java线程崩溃时,它也不会崩溃其他线程 – 尤其是,如果我们正在查看请求处理线程(让main
线程免受这种干扰)
在我之后重复: “这是不同的范例”
大声说出20次左右 – 这是我们目前的口头禅。
如果我们真的必须比较苹果和橘子,那么至less要考虑“正在结果”的共同点在哪里相交。
Java“对象”是Java程序员计算的基本单位。 也就是说,一个对象 (基本上是一个具有胳膊和腿的结构,封装比C ++更加严格 )是你模拟世界的主要工具。 你认为“这个对象知道/拥有Data {X,Y,Z}
并且在它上面执行Functions {A(),B(),C()}
,随身携带Data
,并且可以通过调用与其他对象通信作为公共接口的一部分定义的函数/方法,它是一个名词,这个名词做东西。“ 也就是说,你围绕这些计算单位来定位你的思维过程。 默认的情况是,在对象之间发生的事情是按顺序发生的,而碰撞会中断这个顺序。 他们被称为“对象”,因此(如果我们忽视艾伦·凯的本义),我们就会得到“面向对象”。
Erlang的“进程”是Erlang程序员计算的基本单位。 一个过程 (基本上是一个在自己的时间和空间中运行的自包含的顺序程序)是厄兰格模拟世界的主要工具(1)。 与Java对象如何定义一个封装级别相似,Erlang进程也定义了封装级别,但是在Erlang的情况下,计算单元是完全相互隔离的。 您不能调用另一个进程的方法或函数,也不能访问其中的任何数据,也不能访问与其他进程相同的时序上下文,也不能保证相对的消息接收顺序到可能正在发送消息的其他进程。 他们也可能完全在不同的星球上(并且认为这实际上是有道理的)。 他们可以彼此独立地崩溃,其他进程只有在故意select受到影响的情况下才受到影响(甚至包括消息传递:实质上是注册接收死亡过程中的遗书,本身并不保证以任何forms到达相对于整个系统的顺序,你可能会或可能不会select反应)。
Java直接在复合algorithm中处理复杂性:对象如何协同工作来解决问题。 它被devise为在单个执行上下文中执行此操作,Java中的默认情况是顺序执行。 Java中的多个线程表示多个运行上下文,并且是一个非常复杂的话题,因为不同定时上下文中的影响活动(以及系统作为一个整体:因此是防御性编程,exceptionscheme等)相互影响。 在Java中说“multithreading” 意味着与Erlang不同的地方,事实上这在Erlang中从来就没有说过,因为它始终是基本的情况。 请注意,Java线程意味着隔离与时间有关,而不是内存或可见的引用 – 通过select什么是私有的和什么是公共的,手动控制Java中的可见性; 系统的普遍访问元素必须devise成“线程安全”和可重入的,通过排队机制顺序化,或采用locking机制。 简而言之:调度是线程/并发Java程序中的手动pipe理问题。
Erlang从执行时间(调度),内存访问和参考可见性等方面分离每个进程的运行上下文,并通过完全隔离它来简化algorithm的每个组件。 这不仅仅是默认情况,这是这种计算模式下唯一可用的情况。 这样做的代价是,一旦处理序列的一部分跨过消息障碍,就不会完全知道任何给定操作的顺序 – 因为消息本质上都是networking协议,并且没有方法调用可以保证在给定的上下文。 这就类似于为每个对象创build一个JVM实例,并且只允许它们通过套接字进行通信 – 这在Java中是非常麻烦的,但是Erlang的devise方式是这样的(顺便说一句,这也是概念的基础如果人们放弃stream行的网页包袱,那么编写“Java微服务”就成了stream行语,因为Erlang程序默认是一群微服务。 其全部是关于权衡。
这些是不同的范例。 我们可以发现的最接近的共性是从程序员的angular度来看,Erlang进程类似于Java对象。 如果我们必须find一些东西来比较Java线程……那么我们根本就不会在Erlang中find类似的东西,因为在Erlang中没有这样的比较概念。 打死马: 这是不同的范例 。 如果你在Erlang编写一些不重要的程序,这将变得很明显。
请注意,我在说“这些是不同的范例”,但是甚至没有涉及到OOP和FP的主题。 “Java中的思维”和“Erlang中的思考”之间的区别比OOP和FP更为根本。
虽然Erlang的“面向并行的”或“面向过程”的基础更接近艾伦·凯(Alan Kay)提出的“面向对象”(2)这个术语,但这并不是真正的重点。 凯得到的是,人们可以通过将你的化合物切割成不连续的块来减less系统的认知复杂性,而隔离是必要的。 Java以一种使它在本质上仍然是基本程序性的方式来实现这一点,但是在高级调度闭包(称为“类定义”)上围绕特殊语法构造代码。 Erlang通过为每个对象分割运行上下文来实现这一点。 这意味着Erlang的东西不能调用方法,但Java的东西可以。 这意味着Erlang可以孤立地崩溃,但是Java的东西不能。 这个基本差异带来了大量的影响 – 因此是“不同的范式”。 权衡。
脚注:
- 顺便说一句,Erlang实现了一个“ 演员模型 ”的版本,但是我们并没有使用这个术语,因为Erlang早于这个模型的普及。 当他deviseErlang并写下他的论文时,Joe并不知情。
- 艾伦·凯(Alan Kay) 在他创造 “面向对象” 这个术语时意味着什么 ,最有趣的是他的消息传递(从一个独立的进程以自己的时间和内存到另一个进程的单向通知)VS调用在共享存储器的顺序执行上下文中的函数或方法调用)以及编程语言和下面的实现之间的界线是如何模糊的。
绝对不是。 Java中的所有线程共享相同的地址空间,因此一个线程可能会垃圾其他线程拥有的东西。 在Erlang虚拟机中,这是不可能的,因为每个进程都与其他进程隔离。 这是他们的重点。 任何时候你想要一个进程使用另一个进程来处理数据,你的代码就必须向另一个进程发送消息。 进程之间共享的唯一东西是大的二进制对象,这些是不可变的。
Java进程实际上可以共享内存。 例如,您可以将相同的实例传递给两个独立的线程,两者都可以操纵其状态,从而导致潜在的问题,如死锁 。
另一方面,Elixir / Erlang通过不变性的概念来解决这个问题,所以当你将某个东西传递给一个进程时,它将是原始值的一个副本。
当Java线程死亡时,它也不会影响其他线程
让我问一个反问:为什么你认为Thread.stop()
已经被弃用了十多年? 原因正是上面你的陈述的否定。
给出两个具体的例子:你stop()
一个线程,而它正在执行一些像System.out.println()
或Math.random()
那样无害的声音。 结果:现在这两个function在整个JVM中都被破坏了。 这与您的应用程序可能执行的任何其他同步代码相同。
如果我们正在查看请求处理线程
应用程序在理论上可能被编码为绝对不会使用受锁保护的共享资源; 然而这只会有助于指出Java线程的确切程度。 而所获得的“独立性”只涉及请求处理线程,而不涉及这种应用程序中的所有线程。
为了补充以前的答案,Java线程有两种types:守护进程和非守护进程。
要改变一个线程的types,你可以调用.setDaemon(boolean on)
。 区别在于守护进程线程不会阻止JVM退出。 正如Thread的Javadoc所说:
当运行的所有线程都是守护进程线程时,Java虚拟机退出。
这意味着:用户线程(那些没有专门设置为守护进程的线程)会阻止JVM终止。 另一方面,当所有非守护线程完成时,守护进程线程可以运行,在这种情况下,JVM将退出。 所以,要回答你的问题:你可以启动一个线程,当它完成时不会退出JVM。
至于与Erlang / Elixir的比较,不要忘记: 如前所述,它们是不同的范例。
JVM模仿Erlang的行为并不是不可能的,尽pipe它不是为了它的意图,因此它有很多的权衡。 以下项目试图完成:
- Erjang
- 阿卡
Java线程也不是这样吗? 我的意思是当Java线程崩溃时,它也不会崩溃其他线程
是的,不,我解释说:
-
引用共享内存:Java进程中的不同线程共享整个堆,因此线程可以以大量的计划和非计划的方式进行交互。 然而 ,堆栈中的对象(例如传递给被调用方法的上下文)或
ThreadLocal
是它们自己的线程(除非它们开始共享引用)。 -
崩溃:如果一个线程在Java中崩溃(一个
Throwable
被传播到Thread.run()
,或者某些东西被循环或阻塞),那么这个事件可能不会影响其他线程(例如服务器中的连接池将继续运行)。 但是随着不同的线程交互。 如果其中一个线程exception终止(例如,一个线程试图从另一个没有closures线程的线程的空pipe道中读取),则其他线程将很容易被搁浅。 所以除非开发者高度偏执谨慎,否则很可能会出现副作用。
我怀疑任何其他的范式都是为了完全独立的岛屿而运作。 他们必须以某种方式分享信息和协调。 然后就有机会搞砸了。 只是他们会采取更加防守的方式,“让你less挂绳子”(与指针相同的习惯用法)。