尽管您的谦虚作家坚信底层机制的抽象性,但我选择下一次会议是因为在某些用例中,开发人员的意图与其实现之间的不匹配会导致性能问题。 匹配它们需要更好地了解实际情况。
Jose Paumard从Runnable
synchronize
到parallel()
和atomically()
演讲全部涉及并发编程及其相关的副作用,性能和错误。 标题解释来自于过去与未来: Runnable
是JDK 1.1(1995年)和parallel
从JDK 8(2014?)。 特别是,我们将看到硬件如何影响软件以及软件开发人员的工作方式。
中心问题是如何利用处理器功能? 因此,并行应用程序有什么问题(考虑并发性,同步性和不变性)。 最后,现在和不久的将来,Java开发人员可以使用哪些工具来帮助我们开发并行应用程序?
多线程
在1995年,处理器只有一个核心。 多线程是关于在执行线程之间共享处理器的时间。 当前线程完成时,或者在等待慢速线程或锁定资源时,处理器将执行线程。 此时,并行化是所有处理器在同一时间执行同一条指令,但对不同的数据执行。 这是非常快的,因为线程不需要相互交互(不需要相互传递数据)。
从API的角度来看,Java提供了Runnable
和Thread
。 可以使用关键字synchronized
和volatile
。 在2004年之前,没有必要超越这一范围。
早在2005年,多核处理器就可以普遍使用。 而且,从这一点开始,功率不是来自核心频率的增加(摩尔定律),而是核心数量加倍。 为了解决这个问题,Java 5将java.util.concurrent
引入到表中。 附带说明一下,有一个EDU.oswego向Java 4的反向移植。此软件包中提供了许多新概念。
如今,每台简单的计算机都有2到8个内核。 诸如SPARC T3之类的专业计算机具有128个内核。 明天,规模将达到一万个内核,甚至更多。
多线程问题
- 原始数据的增加使得有必要进行处理
- 比赛条件。 这是Singleton模式和double-check锁定模式(不起作用)中竞争条件的演示。
Java语言规范定义了一个原则:对变量的读取应返回该变量的最后写入值。 为了具有确定性,在访问同一变量之间应该存在一个“之前发生”的链接。 如果没有链接,则无法确定控制流程:糟糕,这里有一个很好的错误定义。 解决方案是在变量上使用关键字
volatile
以创建链接“ happens before”。 不幸的是,它极大地将性能降低到了单个内核的水平。至于具有Singleton实现的唯一方法是:
public enum Singleton { instance ; }
现在,当您深入到硬件级别时,它变得更加复杂。 每个内核都有两个级别的缓存(L1和L2),并且所有内核(L3)之间共享一个缓存。 下表显示了将数据从核心传递到不同的缓存所需的时间:
目的地 | 时间(纳秒) |
---|---|
L1 | 1个 |
L2 | 3 |
L3 | 15 |
内存 | 80 |
总之,无需同步,就可以以最佳速度执行程序。 使用它,您应该考虑处理器管理连接到处理器体系结构的内存(包括缓存)的方式! 这正是我们试图用Java阻止的内容(编写一次,在任何地方运行)。 也许Java尚未准备好走这条路。
java.util.concurrent
Java 5 java.util.concurrent
是启动线程的新方法。 之前,我们实例化了一个新的Runnable
,创建了一个新的Thread
wrapper并start
后者。 Thread的run()方法没有返回值,也不会引发异常。
在Java 5中,我们实例化了Callable
:其call
方法既有返回值,又会引发异常。 我们将其传递给线程执行程序服务,该服务管理其onw线程池(并可能重用已经存在的线程)。 我们将线程管理委托给可以抽象(并适应)底层硬件的服务。 还有其他概念。
- 锁定:将块锁定1个线程。 它提供了在一个方法中获取锁并在另一个方法中释放锁的方法。 此外,还有两种获取锁的方法:
lock()
和tryLock()
。 最后,有一种方法可以拥有一个ReadWriteLock
,就像拥有所需的多个读取器,以及一个阻止读取的写入器一样。 读取不同步,写入同步。 - 信号量:将块锁定为n个线程
- 屏障:同步线程
- 闩锁:打开/关闭所有线程的代码块
此外,还提供了新的集合: BlockingQueue
(和DeQueue
),它们是具有超时方法的固定大小的集合。 它们是线程安全的,用于生产者/消费者实现。
最后,一个CopyOnWriteArrayList
(和CopyOnWriteLinkedList
)提供了并发友好性,使每个线程仅在写入时才能读取和锁定。 不幸的是,在读取次数不多时(对数组进行了复制,并且在写入时更改了内存指针),therey得到了优化。 哈希和集合结构可以实现为以相同的方式操作(复制和更改指针)。
替代synchronize
内存交易
Akka提供了软件事务存储 API。 使用API,我们不应该直接操作对象,而应该引用对象。 此外,我们需要能够为我们管理STM的Atomic对象。 内存是通过乐观锁定进行管理的:不做任何更改,并且在这种情况下可以提交。 最佳用例是访问并发性较低时,因为事务失败时会重播事务。 在高并发访问的情况下,将有很多重播。
一些实现使用锁,而其他一些则不使用。 这些与JLS中的锁语义不同。 另外,某些实现非常消耗内存,并且会随着并发事务的数量而扩展。 英特尔最近宣布,Haswell体系结构将支持事务同步扩展:迟早会对Java语言产生影响。
演员们
Actor是以只读消息形式接收数据的组件。 他们计算结果并将其作为消息发送给其他参与者(最终是其调用者)。 该策略全部基于不变性, 在这种情况下无需并发 。
Akka还提供了Actor API:编写Akka actor与编写Java 5 Callable
非常相似。 此外,CPU使用率与执行时间大致相同(准确地说,无显着差异)。 那么,为什么要使用actoris而不是执行者服务呢? 通过交易参与者引入STM的力量。 提供的示例是银行帐户转帐。
并行运算
在Java 7中,有Fork / Join模式。 另外,也有并行数组,Java 8提供了parallel()
方法。 目标是使阵列和集合上的并行计算自动化。
叉连接
它基于任务。 如果一个任务决定它太大,它将分散其他较小的子任务:这被称为fork。 然后,系统托管的线程池可用,每个线程都有一个任务队列。 当队列中没有更多任务时,线程能够从其他线程队列中获取任务并将其放在自己的队列中。 任务完成后,称为联接的阻塞可以聚合子任务的结果(以前是分叉的)。
结果,任务实现不仅必须描述其业务代码,而且还必须知道如何确定它是否太大以及如何分叉自身。 此外,可以以递归方式或迭代方式使用Fork Join。 与前一种方法相比,后一种方法的性能提高了30%。 结论是仅在不事先知道系统边界的情况下才使用递归。
平行阵列
并行阵列在JSR 166y中进行了描述。 [因此结束我在谈话中的笔记以及电池寿命]#失败
结论
说话者不仅知道他的主题,而且知道如何让听众陷入困境。 除了谈话中描述的要点外,我认为整件事中最重要的一点是他对最后一个问题的回答:
“考虑到Java隔离了开发人员的抽象级别,使用替代语言解决新的多核体系结构是否合乎逻辑?
- 大概是”
[更新]
减轻Olivier Lamy和Benoit Perroud对内存分配的压力
Apache Direct Memory的目标是减少垃圾收集器引起的延迟。 目前在Apache Incubator中。
缓存堆
提醒一下,Java的内存被划分为不同的空间(年轻,使用期限和烫发)。 有些部分分配给JVM,其余部分是本地的,并由网络使用。 堆的问题是它由JVM管理,并且为了释放未使用的对象,必须清理垃圾收集器。 这是一个冻结程序执行并使程序不确定的过程(您不知道GC何时启动和停止)。
由于当今内存非常便宜,因此许多应用程序使用本地缓存来提高性能。 可能有两个缓存:
- 堆上:想想一个大的哈希图。 这会增加GC处理时间。
- 堆外:通过序列化将其存储在本机内存中(这有一定的代价),但会减少堆内存的使用,从而减少垃圾收集器的时间
Apache直接内存
ADM基于ByteBuffer
类:批量分配,然后按需分离。 该体系结构是分层的,顶部是一个Cache API。
分配内存的策略必须解决与磁盘空间碎片完全相同的问题:熔丝字节缓冲区或固定大小的字节缓冲区(内存“丢失”但没有碎片)。
在现实生活中,应该只有一小部分的缓存对象在堆上,其余部分在堆外。 这与Terracotta的BigMemory所做的非常相似。 这意味着人们可以使用EhCache的API并委托给ADM的实现。
下一步包括JSR 107的实现,基准测试,与其他组件(Cassandra,Lucene,Tomcat的集成),热配置,监视和管理功能等。有关更多信息,请参见ADM站点 。
请注意,ADM仅使用Java的公共API( ByteBuffer
)作为掩体,并且没有com.sun
黑客。
演讲非常有趣,是Terracotta产品的不错替代品。 我认为ADM尚未准备好投入生产,但肯定会引起关注。
尽管我不喜欢被JavaScript困扰的应用程序,但事实是,这里有。 最好有一些工具来管理它们,而不要遭受痛苦。 一个管理得当JavaScript应用程序可能等同于一个管理得当的Java应用程序( 邪恶的笑声 )。
小心Romain LinsolasJavaScript代码
从本质上讲,该演讲是关于如何通过Jenkins CI编写JavaScript测试,对其进行分析和管理的。 实际上,只有一小部分已经在JavaScript代码上做到了。
测试JavaScript
Jasmine是JavaScript的行为驱动测试库,而Underscore.js是实用程序库。 两者都将用于创建我们的测试工具。 Akquinet Maven原型可以帮助我们引导项目。
describe
是对测试“类”的引用,而it
是对测试函数的引用。 另外, expect
让我们断言。 beforeEach
扮演Java(JUnit或TestNG)中等效的Before
注释的角色。 其他Jasmine函数模仿它们的Java等效项。
分析JavaScript
让我们在这里保留经过验证的工具。 如果您使用上述Maven原型,那么它已经与Sonar兼容! 为了控制测试范围,可以使用一个不错的库js-test-driver 。 它看起来像一个JUnit。 无需放弃我们先前的Jasmine测试,我们只需要使用js-test-driver启动它们,因此某些先决条件暗示了一些POM更新:
- 茉莉花适配器
- JAR计算覆盖率
- 和可用端口以启动Web服务器
指标可以在Sonar中轻松获得! 通过Maven CLI完成的操作可以在Jenkins中轻松复制。
尽管开发JavaScript很好,但是与我的应用程序方法形成了对立。 至少,当我遇到这些基于JavaScript的应用程序时,我将准备好解决它们。