Java 8会会馆干掉徐真真Scala吗

关于Java和Scala同步机制你不知道的5个真相 - ImportNew
实际上,所有的服务器应用在多线程中都需要某种类型的同步机制。框架已经为我们做了大多数的同步的工作,这些框架包括web服务器、数据库连接客户端、消息框架等。我们可以通过Java和Scala提供的多种组件来写出可靠的多线程应用。这些组件包括,对象池、并发集合类、高级锁、执行上下文等。
为了更好的理解这个问题,接下来首先要探究最常用的同步模式:对象锁。这种机制增强了synchronized 关键字,使它成为Java编程多线程的最流行的习惯用法之一。同时,它也是许多常用复杂模式的基础,包括线程池、连接池、并发集合类等。
synchronized 关键字主要应用于以下两种情境:
作为一种改进方法,使被标记的某一方法在同一时间仅仅能被一个线程执行。
标记一个代码块为临界区块,标记后的代码块在任何时间点仅有一个线程可以执行。
#真相1:同步代码块是通过MonitorEnter和MonitorExit专用字节码指令来实现,这两个字节码指令也是官方参数的一部分。这种实现和其他的锁机制不同,在java.util.concurrent包中的一些方法(在hotspot上的实现)通过结合使用Java代码和使用sun.misc.Unsafe的实现本地调用。
这些指令运行在使用了sychronized上下文的对象上。对于sychronized方法,“this”变量会自动选择锁。对于静态方法,会将锁置于Class对象上。
Sychronized方法有时也会带来比较糟糕的结果,比如当不同的sychronized方法共享一个锁文件时,将会造成一些隐性依赖。如果遇到下面这种情况会变得更糟糕:比如在基类(甚至第三方类库)中声明了sychronized方法,然后又在派生类中增加了一个新的sychronized方法。这将造成整个类的层次结构对同步的隐性依赖,同时也会带来吞吐量的问题甚至死锁。为了避免出现这种问题,推荐使用私有对象作为锁对象,以此来避免意料之外的锁共享和lock溢出。
编译器与同步机制的关系
通过两个字节码指令负责完成同步工作是不同寻常的。通常字节码指令之间相互独立的,通过把值放在线程操作数堆栈上完成字节码之间的相互“通信”。被加锁的对象也预先放在操作数堆栈上,然后通过从变量、字段中取值或者方法反射的方式来从操作数堆栈上返回一个对象。
#真相2:如果仅仅调用两个字节码指令其中之一,而不调用另一个会出现什么情况?仅仅调用monitorExit却不调用MonitorEnter的代码,Java编译器不会编译通过的。即使从JVM角度来看这也是合理的。这种情况的结果就是MonitorExit指令会抛出一个IllegalMonitorStateException异常。
一种更加危险的情形是:如果一个锁通过调用MonitorEnter被获取却不被调用MonitorExit被释放会发生什么?这种情况下,得到锁的线程将会造成其他需要获取此锁的线程无限期挂起。这是毫无意义的。因为锁本身可重入,从某种意义上说,线程之间都是平等的。虽然这个线程现在快乐地执行着,当这个线程下次需要执行这段代码的时候,也需要重新获取这个锁。(译注:换句话说,到时候快乐的可是别人了,你在这干着急吧……)。
解决问题的关键就在这里。为了防止这一切的发生,Java编译器生成了相互配对的enter和exit指令。使用这种方法,一旦执行了进入synchronized的块或方法时,它必须传递一个相对应的MonitorExit给同一个对象。但是,一旦临界区域抛出异常,那么事情将变得非常糟糕。(译注:抛出异常后,程序会退出,monitorExit的字节码将不会被调用)。
public void hello() {
synchronized (this) {
System.out.println(&Hi!, I'm alone here&);
让我们来分析下字节码:
aload_0 //将代码载入操作数堆栈
dup //再次载入
astore_1 //将此备份到保存在寄存器1的一个隐式变量中
monitorenter //从栈中出栈这个值来进入monitor
//真正的边界区域
getstatic Java/lang/System/out LJava/io/PrintS
ldc &Hi!, I'm alone here&
invokevirtual Java/io/PrintStream/println(LJava/lang/S)V
aload_1 //获取寄存器1内的备份
monitorexit //出栈这个变量并退出monitor
goto 14 // 直接跳到最后并退出
// 编译器额外添加的catch声明,如果运行中出现异常,执行以下内容
aload_1 //获取寄存器1内的备份
monitorexit //退出monitor
athrow // 重新抛出异常类,将其载入操作数堆栈
从上面的代码分析中可以看出,编译器解决栈无法释放monitorExit的方法也非常直接:编译器会添加一个隐式的try catch声明来释放锁并抛出异常。
#真相3:另外一个问题是:在进入相应调用的enter之后、exit退出之前,被加锁的对象引用存放在什么地方。值得注意的是,多线程可能并行执行同一个同步块代码,但使用不同的锁对象。如果锁对象是一个方法反射后得到的结果,那么JVM几乎不大可能会再次执行它,因为这可能会改变对象的状态,甚至可能返回的不是同一个对象。当一个变量或者字段在monitor进入之后发生改变时,这种论断也是正确的。
Monitor变量。为了克服这个问题,编译器为方法添加了一个隐性的本地变量来保持锁对象的这个值。这是一种非常聪明的解决方案。相对于保存一个锁对象的引用,这中方法带来的损耗相对更低,因为没有使用并行堆结构来为线程获取锁对象的值(并发结构本身也可能需要加锁)。我第一次发现这个新变量是在构建Takip堆栈分析算法时,看到代码中pop了一个意料之外的变量。
我们应该意识到,所有的工作都是在Java编译器级别完成的。JVM非常乐意通过Monitor进入一个临界区段,但不退出(反之亦然);或者使用不同的对象进入相应的enter和exit的方法。
接下来我们进一步看看JVM级别,锁是如何实现的。在这一节中我们将查看hotspot SE 7的实现。由于锁机制对代码吞吐量有极大的影响,因此JVM做了非常多的优化使得获取锁和释放锁尽可能高效。
#真相4:JVM最强壮的机制之一就是线程的锁偏移(Locking Biasing)。锁是每个Java对象都拥有的本质属性,这就像每个对象都拥有hashcode或者他们类的引用一样。而且无论对象的类型,上述论断都是正确的(如果喜欢你甚至可以使用一个原始数组作为一个锁)。
这些数据存储在每个对象的头部(也称为类的标记)。有些放在对象头部的数据段是保留字段,专门用来存储对象的锁状态。这其中包含着表明对象锁状态的位字段(加锁/未加锁)以及当前拥有这个锁的线程:这个对象的偏移就指向这个线程。
为了给对象头部留出空间,Java线程对象位于VM堆比较低的位置,这样就可以压缩地址的大小,并且节省每个对象的头部的bit位(JVM32位的对应23bits,64位对应54bits)。
64位示意图
当JVM试着去获取一个对象的锁的时候,它将经历悲喜两重天。
#真相5:一旦一个线程成功把自己编程对象锁的拥有者,那么这个线程就成功地获得了锁。这取决与线程对象能否将锁对象头部字段的引用(一个内部Java线程对象的指针)指向自己。
获取锁。获取锁的第一步是使用一个CAS(compare-and-exchange比较并改变)指令。这一步通常非常的高效,因为他一般会被翻译成一个直接的CPU指令(比如cmpxchg)。CAS操作和操作系统特定线程的暂止程序一起为对象同步提供了基础的功能。
如果锁当前可用,或者之前已经对这个线程进行了偏移,那么线程可以立刻获取对象上的锁并且继续执行线程内的程序。如果CAS失败,JVM会执行一轮自旋锁定。这时线程暂止会将线程休眠,直到再次尝试CAS。如果这些尝试失败(意味着更高级别的锁争用),线程会把自己变为挂起状态,同时进入一个线程争用队列开始一系列的自旋锁定。
释放锁。当临界区通过MonitorExit退出时,拥有锁的线程将会尝试能否唤醒等待获取锁的挂起线程。这个过程也被称作选一个“继任者”。这能激活停滞的多个线程,同时也能防止出现——锁已经被释放但其他线程却仍处在暂止状态。
调试服务器多线程问题是非常苦逼、非常困难的,因为它们常常依赖非常极端、非常特殊的时间点以及操作系统算法。这也是当初我们为Takip工作的原因。
如果你有兴趣学习JVM锁是如何实现的,有代码和文档。
猛戳可以看到全方位的关于不同Java同步API和技术。
Scala并发请戳。
原文链接:
- 译文链接: [ 转载请保留原文出处、译者和译文链接。]
关于作者:
(新浪微博:)
可能感兴趣的文章
您要是能把您提出来的问题同时解答下就非常感谢了~
zhangliao613
关于ImportNew
ImportNew 专注于 Java 技术分享。于日 11:11正式上线。是的,这是一个很特别的时刻 :)
ImportNew 由两个 Java 关键字 import 和 new 组成,意指:Java 开发者学习新知识的网站。 import 可认为是学习和吸收, new 则可认为是新知识、新技术圈子和新朋友……
新浪微博:
推荐微信号
反馈建议:@
广告与商务合作QQ:
– 好的话题、有启发的回复、值得信赖的圈子
– 写了文章?看干货?去头条!
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 活跃 & 专业的翻译小组
– 国内外的精选博客文章
– UI,网页,交互和用户体验
– JavaScript, HTML5, CSS
– 专注Android技术分享
– 专注iOS技术分享
– 专注Java技术分享
– 专注Python技术分享
& 2017 ImportNew学生服务号
在线咨询,奖学金返现,名师点评,等你来互动如何评价Linkedin决定逐渐减少Scala转而使用Java8的决定? - 知乎751被浏览37475分享邀请回答16125 条评论分享收藏感谢收起编程没有银弹:探讨 Java 8 新增特性的优缺点 - 编程语言 - ITeye资讯
相关知识库:
Java 8或许是 ,最初定于今年的9月份发布,但由于一系列的安全漏洞问题,目前已推迟到明年的3月份。
Java 8试图“创新”,根据 ,就是把其他框架或语言里成熟的特性“偷”进来。在新版本发布之前,Java社区就已经开始讨论Lambda项目、Streams、函数式接口等其他好东西。下面就让我们一起来看下这些伟大的功能,看看它们各自的优缺点,好让你更好地应用在项目中。
集合(Collections)的改进也是Java 8的一大亮点,而让集合越来越好的核心组件则是“Stream”。它与java.io包里的InputStream和OutputStream是完全不同的概念,它是一个全新的概念,大家不要混淆。
此外,Stream的出现也并不是要取代ArrayLists或其他集合,它提供了一种操作大数据接口,让数据操作更容易和更快。Stream是一次性使用对象,一旦被遍历,就无法再次遍历。在遍历时,它具有过滤、映射以及减少遍历数等功能。每个Stream都有两种模式:顺序执行和并行执行,其能够利用多核处理器的优势,并可以使用 并行方式来拆分任务和加速处理过程。
List &Person& people = list.getStream.collect(Collectors.toList());
List &Person& people = list.getStream.parallel().collect(Collectors.toList());
顾名思义,当使用顺序方式去遍历时,每个item读完后再读下一个item。而使用并行去遍历时,数组会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。
并行流实例:
List originalList = someD
split1 = originalList(0, mid);
split2 = originalList(mid,end);
new Runnable(split1.process());
new Runnable(split2.process());
List revisedList = split1 + split2;
由于一个Stream只能被遍历一次,通常会返回另外一个Stream,可以使用终端方法(terminal method)来获取有用的结果,终端方法可以是sum()、collect()或toArray()等。在Stream被终止之前,操作的结果不会被实现。
Double result = list.getStream().mapToDouble(f -& f.getAmount()).sum();
List&Person& people = list.getStream().filter(f -& f.getAge() & 21).collect(Collectors.toList());
该功能最大的好处是允许使用多核处理器来处理集合,这样处理速度会更加快速。而最主要的问题则是可读性。随着流链的加长,很有可能影响可读性。其它问题则来源于内置的新东西来支持这个新路径,这些是功能接口和Lambda。
函数式接口
在Java 8里将会有一个全新的功能——函数式接口(functional interfaces),就是可以在接口里面添加默认方法,并且这些方法可以直接从接口中运行。
这样就可以在接口中实现集合的向后兼容,并且无需改变实现这个方法的类,就可以让Stream放置到接口中。一般而言,在接口中创建一个默认方法,然后实现该接口的所有类都可以使用Stream(无论是默认方法还是非默认方法)。
基本上就是一种多继承形式,这样就变成了实现者之间的问题,作为实现人员,必须重写这些方法,他们可以选择使用超方法(supermethod),这也就意味着,许多实现接口的类需要改写。
这有可能是Java 8里最让人关心的细节,也许Java 8里的函数式接口对于熟悉Scala的开发者来说不算新功能,但是他们可能会拿函数式接口与Scala的特征进行比较。然而,两者之间不同的是:Java 8里的函数式接口不能将一个引用指向实现类,而Scala允许通过self关键字来实现该操作。会有一些语言狂热者说,Java 8里的函数式接口只允许多继承行为,而不是状态。而Scala里的多继承特征既可以是行为也可以是状态。
在Java里实现事务和其它项目,我们一般会使用 或 的扩展类来构建动态代理和字节码操作。而Scala的特行可以让我们更直接地实现。
一方面,函数式接口可能会被以继承方式滥用,另一方面,它们尽量不与Scala特征重复。
Java 8的另一大亮点是引入Lambda表达式,使用它设计的代码会更加简洁。当开发者在编写Lambda表达式时,也会随之被编译成一个函数式接口。下面这个例子就是使用Lambda语法来代替匿名的内部类,代码不仅简洁,而且还可读。
没有使用Lambda的老方法:
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
System.out.println(“Action Detected”);
使用Lambda:
button.addActionListener(e -& {
System.out.println(“Action Detected”);
让我们来看一个更明显的例子。
不采用Lambda的老方法:
Runnable runnable1 = new Runnable() {
public void run() {
System.out.println("Running without Lambda");
使用Lambda:
Runnable runnable2 = () -& { System.out.println("Running from Lambda"); };
正如你所看到的,使用Lambda表达式不仅让代码变的简单、而且可读、最重要的是代码量也随之减少很多。然而,在某种程度上,这些功能在Scala等这些JVM语言里已经被广泛使用。
并不奇怪,Sclala社区是难以置信的,因为许多Java 8里的内容看起来就像是从Scala里搬过来的。在某种程度上,Java 8的语法要比Scala的更详细但不是很清晰,但这并不能说明什么,如果可以,它可能会像Scala那样构建Lambda表达式。
一方面,如果Java继续围绕Lambda来发展和实现Scala都已经实现的功能,那么可能就不需要Scala了。另一方面,如果它只提供一些核心的功能,例如帮助匿名内部类,那么Scala和其他语言将会继续茁壮成长,并且有可能会凌驾于Java之上。其实这才是最好的结果,有竞争才有进步,其它语言继续发展和成长,并且无需担心是否会过时。
Time在Java里已有很长一段时间,首先出现的java.util.Date这个包,其次还有java.sql.Date、Calendar。但处理时间和日期需要大量的monkey代码,因此,像Joda Time等第三方库因此诞生。姗姗来迟,Oracle终于决定在Java里添加一个 包来清理各种时间接口。它看起来很符合现在开发者的胃口,拥有各种各样的时间API。
Java API可以处理一些时空连续体方面的特性,比如距离、质量、重量等,这是值得称赞的,但我仍然认为 会处理得更好。我认为Java API需要好好地修剪而不是添加更多的东西,并且首先Java API应该对这些基本元素提供标准的兼容。
Nashorn是Rhino的接替者,该项目的目的是基于Java实现一个轻量级高性能的JavaScript运行环境。
JDK 7中添加了invokeDynamic,其主要是用来支持非Java语言,尤其是动态语言。而JDK 8中的Nashorn将会给开发者提供一个更加实用的JavaScript实现。事实上,Oracle已经有了他自己的Node.js实现,叫做Node.jar。这似乎比在Java里运行JavaScript更加吸引人。
Accumulators
自从JDK中集成了 java.util.concurrent以来,该特性并没有停止发展。相反,JDK 8将构建于JDK 7和fork/join框架之上,并通过加法器(adders)和累加器(Accumulators)得到了进一步的发展。
首先是同步。但是,如果你使用同步在多线程之间进行增量计数,那么同步有可能难以负担。在Java 6中通过让非竞争锁更廉价(cheap)来使同步不那么难以负担。其中大多数会使用Vector来提升老应用程序性能,几乎每一个单线程都受到了Java Activation Framework的影响。
Java.util.concurrent包使得线程池和其他相对复杂的多线程结构变得更好,但是,倘若你想要通过跨线程来增加一个变量,那么就有点大材小用了。对此,我们采用一种比真正的锁更轻更快的原子。在JDK 8中,我们采用Accumulators和adders,这些要比原子轻量多了,对于大多数异构代码来说,这些足以满足它们的需求,如果线程太多,那么可以增加一个计数器。但想要看到类似map/reduce实现或统计跨线程之间的总和,你仍然需要使用原子,因为如果要读取这些跨线程的值,累积的顺序是无法得以保证的。
HashMap修复
在Java中使用String.hashCode()实现已是大家熟知的bug。如果在特定的代码中引入HashMap,可能会导致拒绝服务攻击。基本上,如果有足够多的参数hash到相同值,那么可能会消耗过多的CPU时间。
通常,HashMap bucket采用链表的方式来存储map条目。使用此算法存在大量的冲突,并且增加了O(1)到O(N)这种哈希变化的复杂性,为了解决这一问题,通过采用平衡tree算法来降低复杂度。
SNI是 (Server Name Identification)的缩写,由于大多数公共网站的访客数量不是太多,几乎很少能达到数百万用户。很多网站都使用相同的IP地址和基于名字的虚拟主机,比如我访问 和 ,最后的网址是一样的,但访问的主机名是不一样的,所以我有可能会访问到不同的Web页面。然而,因为SSL,我可能无法分享IP地址。由于HTTP主机头是建立在基于命名的虚拟主机上,并且主机也是依赖SSL来实现加密/解密的,所以,不得不为每个SSL证书申请不同的IP地址。
在最近几年都是采用SNI来解决这一问题的,Java也不例外。这种方式得到了大多数浏览器的支持,现在Apache和Java也支持它。这意味着过不了多久,我们就可以看到Apache和基于Java的服务器使用Oracle的SSL实现来支持SNI,称作 。
总之,Java 8包含了一大堆非常实用的特性,这也是许多开发者想使用最新版本的原因之一。在我看来,Stream是最好的一个特性。但愿并行集合也能够为其进程性能带来一些提升。而函数式接口可能并不会像预期中的那样好用,万一使用不当,可能会给开发者带来很多麻烦。
本文只是总结了部分Java 8新特性,我们相信,在发布的时候将会有更多新特性与大家见面。你可以通过Simon Ritter在大会上的演讲PPT来了解目前已经添加到Java 8中的55个新特性。
至于该如何取舍,各位开发者应该根据自己的实际需求去研究和使用,并不是所有的新特性就是好的,它们也存在优缺点。(编译:张红月/责编:王果)
英文来源:
clxy 写道icefishc 写道几乎所有主流现代语言都支持的东西, 在java圈子里还经常能看到有人反对。& 误会了,我不是反对。只是觉得难看。还是有那么点区别的哈。比如泛型的&&,我还是觉得难看,虽然其实觉得很好用。再比如,我杜撰的段代码
List&Person& people = Arrays.asList(new Person[]{
person1, person2, new Person(i++)
}).getStream().filter(f -& f.getAge() & 21).collect(Collectors.toList());
您说,这TYYD是标点符号们在玩过家家吗?!好比一个原本话说得很清晰的孩子,看到“所有主流现代”都在玩手势,于是也开始跟着比划起来了,还越来越起劲...
引用使用Lambda表达式不仅让代码变的简单、而且可读、最重要的是代码量也随之减少很多。Lambda, Scala 语法简洁了,,,,但可读性我感觉是越来越差,,,特别是 Scala 语法看得一头雾水。
引用使用Lambda表达式不仅让代码变的简单、而且可读、最重要的是代码量也随之减少很多。Lambda, Scala 语法简洁了,,,,但可读性我感觉是越来越差,,,特别是 Scala 语法看得一头雾水。
要不要简称为 J8呢?
lambda,无非就是用符号把以前的那些代码给封装了一下嘛。类似javascrip与JQuery一样。本人瞎说,请勿喷。。。。
bug很多,坐等java9
“使用Lambda表达式不仅让代码变的简单、而且可读、最重要的是代码量也随之减少很多”连主次关系都没高对。
iminto 写道flashing 写道icefishc 写道SMCwwh 写道clxy 写道引用...我认为Java API需要好好地修剪而不是添加更多的东西...这才是最最重要的部分啊...Stream比较酷,一个内置轻量Map/Reduce实现啊!函数式接口,这个整个就是自打脸...Lambda...好看?!老了...好看?!个毛!我现在看Lambda也很别扭,估计后面用习惯了就不会了把,毕竟确实使代码更简洁了。几乎所有主流现代语言都支持的东西, 在java圈子里还经常能看到有人反对。& 反对不至于吧,应该是觉得做的太难看了。groovy的闭包就好得多。说实话,其他语言里,闭包什么的也不受欢迎,使用率也很低的这个,太武断了啊,闭包简直无处不在啊。
flashing 写道icefishc 写道SMCwwh 写道clxy 写道引用...我认为Java API需要好好地修剪而不是添加更多的东西...这才是最最重要的部分啊...Stream比较酷,一个内置轻量Map/Reduce实现啊!函数式接口,这个整个就是自打脸...Lambda...好看?!老了...好看?!个毛!我现在看Lambda也很别扭,估计后面用习惯了就不会了把,毕竟确实使代码更简洁了。几乎所有主流现代语言都支持的东西, 在java圈子里还经常能看到有人反对。& 反对不至于吧,应该是觉得做的太难看了。groovy的闭包就好得多。说实话,其他语言里,闭包什么的也不受欢迎,使用率也很低的
哈,你说的30行太极端了,30行和3000行的取舍比较简单,肯定不会选择3000行。但是310行和360行代码的区别就没有那么大,与其追求把330行代码简化成310行,不如想办法在这310行代码中,让思路变得清晰,甚至如果为了让代码更清晰,变成了360,我觉得都能接受。我的意思很简单,为了让思路清晰,逻辑清晰,可以去想办法让代码行数少一些,但是如果思路还是那样,逻辑还是那样,只是减少代码行数,甚至逻辑混乱,理解困难,那么不减少也罢。
windshome 写道现代语言演变的趋势真不好。非要追求什么代码清晰,其实是否清晰,不在于需要些的代码行数多,还是行数少。关键是思路清晰,易于理解,过于追求行数是个很不好的事情。代码的清晰,最终呈现出来的就是方法有多少行?类有多少行?30行的代码远比3000行的代码要适合阅读方法过大或类过大,绝对需要考虑解构了
现代语言演变的趋势真不好。非要追求什么代码清晰,其实是否清晰,不在于需要些的代码行数多,还是行数少。关键是思路清晰,易于理解,过于追求行数是个很不好的事情。
String的hashcode方法的bug能否讲清楚点?
icefishc 写道SMCwwh 写道clxy 写道引用...我认为Java API需要好好地修剪而不是添加更多的东西...这才是最最重要的部分啊...Stream比较酷,一个内置轻量Map/Reduce实现啊!函数式接口,这个整个就是自打脸...Lambda...好看?!老了...好看?!个毛!我现在看Lambda也很别扭,估计后面用习惯了就不会了把,毕竟确实使代码更简洁了。几乎所有主流现代语言都支持的东西, 在java圈子里还经常能看到有人反对。& 反对不至于吧,应该是觉得做的太难看了。groovy的闭包就好得多。
更新的好快啊,java7都没怎么用,8就出来了
感觉不错,支持^_^
哥其实是来看语言之间互喷的。
stream?? C# 的东西又再次出现了.... 这哪是Java,明明是Java#嘛,
& 上一页 1
相关资源推荐}

我要回帖

更多关于 快穿之干掉情敌 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信