Archive for the ‘ 本站原创 ’ Category

支持生产阻塞的线程池

在各种并发编程模型中,生产者-消费者模式大概是最常用的了。在实际工作中,对于生产消费的速度,通常需要做一下权衡。通常来说,生产任务的速度要大于消费的速度。一个细节问题是,队列长度,以及如何匹配生产和消费的速度。

一个典型的生产者-消费者模型如下:

在并发环境下利用J.U.C提供的Queue实现可以很方便地保证生产和消费过程中的线程安全。这里需要注意的是,Queue必须设置初始容量,防止生产者生产过快导致队列长度暴涨,最终触发OutOfMemory。 Read more

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 支持生产阻塞的线程池

从Java视角理解系统结构(三)伪共享

从Java视角理解系统结构连载, 关注我的微博(链接)了解最新动态

从我的前一篇博文中, 我们知道了CPU缓存及缓存行的概念, 同时用一个例子说明了编写单线程Java代码时应该注意的问题. 下面我们讨论更为复杂, 而且更符合现实情况的多核编程时将会碰到的问题. 这些问题更容易犯, 连j.u.c包作者Doug Lea大师的JDK代码里也存在这些问题.

MESI协议及RFO请求
前一篇我们知道, 典型的CPU微架构有3级缓存, 每个核都有自己私有的L1, L2缓存. 那么多线程编程时, 另外一个核的线程想要访问当前核内L1, L2 缓存行的数据, 该怎么办呢?
有人说可以通过第2个核直接访问第1个核的缓存行. 这是可行的, 但这种方法不够快. 跨核访问需要通过Memory Controller(见上一篇的示意图), 典型的情况是第2个核经常访问第1个核的这条数据, 那么每次都有跨核的消耗. 更糟的情况是, 有可能第2个核与第1个核不在一个插槽内.况且Memory Controller的总线带宽是有限的, 扛不住这么多数据传输. 所以, CPU设计者们更偏向于另一种办法: 如果第2个核需要这份数据, 由第1个核直接把数据内容发过去, 数据只需要传一次。

Read more

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 从Java视角理解系统结构(三)伪共享

Java视角理解系统结构

  1. 从Java视角理解系统结构(一)上下文切换
  2. 从Java视角理解系统结构(二)CPU缓存
  3. 从Java视角理解系统结构(三)伪共享

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: Java视角理解系统结构

从Java视角理解系统结构(二)CPU缓存

从Java视角理解系统结构连载, 关注我的微博(链接)了解最新动态

众所周知, CPU是计算机的大脑, 它负责执行程序的指令; 内存负责存数据, 包括程序自身数据. 同样大家都知道, 内存比CPU慢很多. 其实在30年前, CPU的频率和内存总线的频率在同一个级别, 访问内存只比访问CPU寄存器慢一点儿. 由于内存的发展都到技术及成本的限制, 现在获取内存中的一条数据大概需要200多个CPU周期(CPU cycles), 而CPU寄存器一般情况下1个CPU周期就够了.

CPU缓存
网页浏览器为了加快速度,会在本机存缓存以前浏览过的数据; 传统数据库或NoSQL数据库为了加速查询, 常在内存设置一个缓存, 减少对磁盘(慢)的IO. 同样内存与CPU的速度相差太远, 于是CPU设计者们就给CPU加上了缓存(CPU Cache). 如果你需要对同一批数据操作很多次, 那么把数据放至离CPU更近的缓存, 会给程序带来很大的速度提升. 例如, 做一个循环计数, 把计数变量放到缓存里,就不用每次循环都往内存存取数据了. 下面是CPU Cache的简单示意图.
Read more

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 从Java视角理解系统结构(二)CPU缓存

阿里内贸团队敏捷实践(三)结对编程

原文发表于《程序员》杂志2012年2月刊

本文主要从提升项目质量、促进知识传递及减少项目风险等角度出发,讲述作者所在团队在结对编程实践中的一些经历,以及如何避免或减少其所带来的负面影响。

你了解结对编程吗?你尝试过结对编程实践吗?也许你还未曾尝试甚至还不曾了解,那么我们一起来学习和了解敏捷结对编程实践,相信对敏捷感兴趣的你会有收获。 Read more

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 阿里内贸团队敏捷实践(三)结对编程

话说模式匹配(4) scala里的赋值语句都是模式匹配吗?

先抛个问题,下面的语句是否都合理(编译通过),哪些会引起模式匹配?

scala> val a = 100 
scala> val A = 100 
scala> val a@b = 100
scala> val (a,b) = (100,200)
scala> val (a,B) = (100,200)    //第二个变量大写
scala> val Array(a,b) = Array(100,200)
scala> val Array(a,B) = Array(100,200) 

scala> object Test { val 2 = 2 } 
scala> object Test { val 2 = 3 } 

我们先看看其他语言(对scala有影响的)有关赋值语句的定义:

1) 在 ML 语言里,对赋值语句的定义:
val P = E

表示定义了模式P中的变量,并赋予它们表达式E中相应的值。

2) 在Erlang中等号 = 表示一个模式匹配操作

在这两种语言中,赋值语句都明确的定义为模式匹配,那么scala中,所有的赋值语句是否都是模式匹配呢?
尤其scala可以说在函数式风格上与ML(及其家族)语言有某种血缘,在这一点上是否也与ML完全一致呢?

先分析一下上面的每条赋值语句:val a = 100val A = 100是直观且没有歧义的。

val a@b = 100 是什么意思?回忆一下第一篇里讲过的“变量绑定模式”,当时的例子有点复杂,重新理解一下:

//给"hello"字符串对象用v1这个变量名
scala> "hello" match { case v1 => println(v1) }

//变量绑定模式,把变量v2 绑定在v1这个模式上
scala> "hello" match { case v2@v1 => println(v2) }

上面的例子中,第一行中v1是个变量模式。 第二行中v2是一个新的变量,只有在v1这个模式匹配成功的情况下,才会把自己绑定到v1上,而v1因为是一个变量模式,它总能匹配成功,所以这里v2也会绑定到”hello”对象上。变量绑定模式通常不会这么使用,更多用在绑定到一个复合结构的模式上,如:

scala> List(1,List(2,3)) match { case List(_, x@List(2,_*)) => println(x.size) }
2

把变量x绑定到了嵌套的 List(2,3) 这个对象上

但赋值语句val a@b = 100 跟上面的有关系么?我们通过ToolBox看看它”脱糖”后的语法树:

scala> tb.parse("val a@b=100")
res13: tb.u.Tree =
{
    <synthetic> private[this] val x$3 = 100: @scala.unchecked match {
        case (a @ (b @ _)) => scala.Tuple2(a, b) //这一句
    };
    val a = x$3._1;
    val b = x$3._2
}

有注释的那一句里面把a,b两个局部变量绑定到通配符”_”上,而这个通配符模式case _ => 可以匹配任何对象,所以相当于把a,b两个变量绑定到了100这个对象上,并产生了一个二元组记录这两个局部变量值。最终把二元组里的值分别赋给了我们定义的a,b两个变量。

接下来的val (a,b) = (100,200) 这个赋值也容易理解,把二元组里的值分别赋给a,b两个变量么,也是经过模式匹配的么?继续用ToolBox分析:

scala> tb.parse("val (a,b)=(100,200)")
res14: tb.u.Tree =
{
    <synthetic> private[this] val x$4 = scala.Tuple2(100, 200): @scala.unchecked match {
        case scala.Tuple2((a @ _), (b @ _)) => scala.Tuple2(a, b)
    };
    val a = x$4._1;
    val b = x$4._2
}

看到了,是一个构造器模式与变量绑定模式的混合模式匹配。

再下一个val (a,B) = (100,200) 这个与上一个有区别么?回顾一下第一篇里讲到的“常量模式”:当变量大写时将被对待为常量模式,也就是说 大写B 和上面的 小写b 是两种不同的模式!!

scala> tb.parse("val (a,B)=(100,200)")
res15: tb.u.Tree =
val a = scala.Tuple2(100, 200): @scala.unchecked match {
    case scala.Tuple2((a @ _), B) => a
} 

大写B在这里当作常量来解析,但又找不到B这个变量(除非之前有定义过),就报错了:

scala> val (a,B) = (100,200)
<console>:8: error: not found: value B
   val (a,B) = (100,200)
          ^

后边两个Array的赋值语句与这两个类似,小括号写法只是元组(Tuple)的语法糖而已。

最后,真正有趣,且会让新手崩溃的情况 object Test { val 2 = 2 } 为什么这个编译和初始化都没问题?

scala> object Test { val 2 = 2 }
defined module Test

scala> Test
res16: Test.type = Test$@3042dc22

简直逆天,难道这个背后也与模式匹配有关系么?

scala> tb.parse(" object Test { val 2 = 2 }")
res0: tb.u.Tree =
object Test extends scala.AnyRef {
    def <init>() = {
        super.<init>();
        ()
    };
    <synthetic> private[this] val x$1 = 2: @scala.unchecked match {
        case 2 => ()
    }
}

确实又是一个常量模式匹配,2匹配2,成功。

同理,下一个 object Test { val 2 = 3 } 也是个常量模式匹配,但为何明显不匹配,却可以编译时成功,而运行时时才报错呢?

scala> object Test { val 2 = 3 }
defined module Test

scala> Test
scala.MatchError: 3 (of class java.lang.Integer)
    at Test$.<init>(<console>:8)

这是因为object 是惰性初始化的原因(lazy),如下:

// 对下面的单例
object Test { val a = 2 }

$ scalac -Xprint:jvm A.scala
package <empty> {
  object Test extends Object {
    private[this] val a: Int = _;
    <stable> <accessor> def a(): Int = Test.this.a;
    def <init>(): Test.type = {
        Test.super.<init>();
        Test.this.a = 2;  //在初始化时才对成员赋值
        ()
    }
  }
}

在对多个变量赋值,或变量中有@符合,导致模式匹配还好理解,但”2=2″也引起模式匹配就会让我产生疑问:
是否所有的赋值语句都是模式匹配?

为了验证,通过编译选项查看val a=2 这样对单个变量的赋值却没有看到模式匹配。
另外,如果单个变量也是模式匹配,为何大写字母val A=2没问题?假设对单个变量赋值也是模式匹配,那岂不无法定义大写的变量了;肯定是有区别的,但又怎么区分的?

我最初遇到这个困惑,在邮件列表里问了这个问题,得到了一些回复,并且有人给了一个老帖子链接说早就讨论过val 1=2这个话题了:http://thread.gmane.org/gmane.comp.lang.scala.user/44036

在那个帖子里,martin也回复了为何 val 1=2是模式匹配,并且为何不把这种情况作为错误给修复掉:

A value definition is of the form

val <pattern> = <expression> // 这个同ML和Erlang语言
1 is a <pattern>

There is one edge case:
If the pattern is a single variable (upper or lower case or backquoted), then it is always treated as a variable, not a constant. Otherwise, there would be no way to define such a value.

只有一种边缘情况:如果模式是一个单独的变量(大写、小写、或用反引号引起来的),那么它总被当作变量,而非常量。否则就没法定义这样的一个值。

所以1=2, "a"="b" 这样的赋值语句虽然是一个变量,但变量名称不符合上面的约束,产生了模式匹配。至于为何不修复这个问题(直接在编译时报错),也可以从这个帖子的线索中找到原因。

话说模式匹配(3) 模式匹配的核心功能是解构

http://www.artima.com/scalazine/articles/pattern_matching.html
这篇文章是odersky谈scala中的模式匹配的一段对话,我做了部分片段翻译(不是连贯的):

模式可以嵌套,就像表达式嵌套,你可以定义深层的模式,通常一个模式看起来就像一个表达式。它基本上就是同一类事情。
它看起来像一个复杂的对象树构造表达式,只是漏掉了new关键字。事实上在scala当你构造一个对象,你不需要new关键字
然后你可以在一些地方用变量做站位符替代对象树上实际的东西。

本质上,当你需要通过外部来构造对象图,模式匹配是必要的,因为你不能对这些对象添加方法
有很多场景的例子,XML是一个,所有解析过的数据落入不同的分类。
举例,一个标准的场合是当你用编译器解析抽象语法树的时候模式匹配是必要的。 Read more

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 话说模式匹配(3) 模式匹配的核心功能是解构

话说模式匹配(2) scala里是怎么实现的?

在这篇martin和另外两位模式匹配领域专家的论文里说了模式匹配的几种实现方式,以及scala是选择哪种方式来实现的。
http://lampwww.epfl.ch/~emir/written/MatchingObjectsWithPatterns-TR.pdf
我引用了里面的一些描述。

在面向对象的程序中数据被组织为一级一级的类(class)
面向对象语言在模式匹配方面的问题在于如何从外部探测这个层级。

有6种实现模式匹配的方法:
1) 面向对象的分解 (decomposition)
2) 访问器模式 (visitor)
3) 类型测试/类型造型 (type-test/type-cast)
4) typecase
5) 样本类 (case class)
6) 抽取器 (extractor) Read more

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 话说模式匹配(2) scala里是怎么实现的?

话说模式匹配(1) 什么是模式?

一些刚从java转到scala的同学在开发的过程中犹如深陷沼泽,因为很多的概念或风格不确定,scala里有很多的坑,模式匹配也算一个。我整理了一下自己所理解的概念,以及一些例子。这个系列最好有些scala的基本经验,或者接触过一些其他函数式语言。

要理解模式匹配(pattern-matching),先把这两个单词拆开,先理解什么是模式(pattern),这里所的模式并不是设计模式里的模式。
而是数据结构上的,这个模式用于描述一个结构的组成。

我们很容易联想到“正则表达”里的模式,不错,这个pattern和正则里的pattern相似,不过适用范围更广,可以针对各种类型的数据结构,不像正则表达只是针对字符串。

Read more

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 话说模式匹配(1) 什么是模式?

深入理解Java内存模型(七)——总结

处理器内存模型

顺序一致性内存模型是一个理论参考模型,JMM和处理器内存模型在设计时通常会把顺序一致性内存模型作为参照。JMM和处理器内存模型在设计时会对顺序一致性模型做一些放松,因为如果完全按照顺序一致性模型来实现处理器和JMM,那么很多的处理器和编译器优化都要被禁止,这对执行性能将会有很大的影响。

根据对不同类型读/写操作组合的执行顺序的放松,可以把常见处理器的内存模型划分为下面几种类型:

  1. 放松程序中写-读操作的顺序,由此产生了total store ordering内存模型(简称为TSO)。
  2. 在前面1的基础上,继续放松程序中写-写操作的顺序,由此产生了partial store order 内存模型(简称为PSO)。
  3. 在前面1和2的基础上,继续放松程序中读-写和读-读操作的顺序,由此产生了relaxed memory order内存模型(简称为RMO)和PowerPC内存模型。 Read more

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 深入理解Java内存模型(七)——总结

深入理解Java内存模型(六)——final

本文属于作者原创,原文发表于InfoQ:http://www.infoq.com/cn/articles/java-memory-model-6

与前面介绍的锁和volatile相比较,对final域的读和写更像是普通的变量访问。对于final域,编译器和处理器要遵守两个重排序规则:

  1. 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
  2. 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。 Read more

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 深入理解Java内存模型(六)——final

深入理解Java内存模型(五)——锁

本文属于作者原创,原文发表于InfoQ:http://www.infoq.com/cn/articles/java-memory-model-5

锁的释放-获取建立的happens before 关系

锁是java并发编程中最重要的同步机制。锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息。下面是锁释放-获取的示例代码:

class MonitorExample {
    int a = 0;

    public synchronized void writer() {  //1
        a++;                             //2
    }                                    //3

    public synchronized void reader() {  //4
        int i = a;                       //5
        ……
    }                                    //6
}

假设线程A执行writer()方法,随后线程B执行reader()方法。根据happens before规则,这个过程包含的happens before 关系可以分为两类:

  1. 根据程序次序规则,1 happens before 2, 2 happens before 3; 4 happens before 5, 5 happens before 6。
  2. 根据监视器锁规则,3 happens before 4。
  3. 根据happens before 的传递性,2 happens before 5。

Read more

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 深入理解Java内存模型(五)——锁

Java并发包中的同步队列SynchronousQueue实现原理

作者:一粟

介绍

Java 6的并发编程包中的SynchronousQueue是一个没有数据缓冲的BlockingQueue,生产者线程对其的插入操作put必须等待消费者的移除操作take,反过来也一样。

不像ArrayBlockingQueue或LinkedListBlockingQueue,SynchronousQueue内部并没有数据缓存空间,你不能调用peek()方法来看队列中是否有数据元素,因为数据元素只有当你试着取走的时候才可能存在,不取走而只想偷窥一下是不行的,当然遍历这个队列的操作也是不允许的。队列头元素是第一个排队要插入数据的线程,而不是要交换的数据。数据是在配对的生产者和消费者线程之间直接传递的,并不会将数据缓冲数据到队列中。可以这样来理解:生产者和消费者互相等待对方,握手,然后一起离开。

SynchronousQueue的一个使用场景是在线程池里。Executors.newCachedThreadPool()就使用了SynchronousQueue,这个线程池根据需要(新任务到来时)创建新的线程,如果有空闲线程则会重复使用,线程空闲了60秒后会被回收。 Read more

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: Java并发包中的同步队列SynchronousQueue实现原理

从Java视角理解系统结构(一)CPU上下文切换

作者:Minzhou  本文是从Java视角理解系统结构连载文章

在高性能编程时,经常接触到多线程. 起初我们的理解是, 多个线程并行地执行总比单个线程要快, 就像多个人一起干活总比一个人干要快. 然而实际情况是, 多线程之间需要竞争IO设备, 或者竞争锁资源,导致往往执行速度还不如单个线程. 在这里有一个经常提及的概念就是: 上下文切换(Context Switch).

上下文切换的精确定义可以参考: http://www.linfo.org/context_switch.html。下面做个简单的介绍. 多任务系统往往需要同时执行多道作业.作业数往往大于机器的CPU数, 然而一颗CPU同时只能执行一项任务, 如何让用户感觉这些任务正在同时进行呢? 操作系统的设计者巧妙地利用了时间片轮转的方式, CPU给每个任务都服务一定的时间, 然后把当前任务的状态保存下来, 在加载下一任务的状态后, 继续服务下一任务. 任务的状态保存及再加载, 这段过程就叫做上下文切换. 时间片轮转的方式使多个任务在同一颗CPU上执行变成了可能, 但同时也带来了保存现场和加载现场的直接消耗。
Read more

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 从Java视角理解系统结构(一)CPU上下文切换

聊聊我对Java内存模型的理解

所有的编程语言中都有内存模型这个概念,区别于微架构的内存模型,高级语言的内存模型包括了编译器和微架构两部分。我试图了解了Java、C#和Go语言的内存模型,发现内容基本大同小异,只是这些语言在具体实现的时候略有不同。

我们来看看Java内存模型吧,提到Java内存模型大家对这个图一定非常熟悉: Read more

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 聊聊我对Java内存模型的理解

return top