出差北京,趁回杭州以前去了趟海淀,顺手带了本以前一直想买的虚拟机并发编程,虽然这本书在amazon中国上的评价通常,但看看总仍是有些收获吧,可能那些说书通常的都是大神吧。书应该是刚上新的,放在了最显眼的位置。一晚上火车,看了前三章以为不错,仍是忍不住写点儿笔记。 java
书的前言部分推荐了两本书《Java Concurrency in Practice》和《Concurrent Programming in Java》,这两本书主要对JDK提供解决方案,并在内存模型和一致性上进行了比较全面的叙述。而《虚拟机并发编程》则更重于对实际问题的分析和一些经验性的结论。好比这本书会从IO密集型与计算密集型等方面对并行程序进行分析。 编程
书主要描述了三种并发的解决方案,java JDK并发模型,软件事务内存(STM)和基于角色的并发模型(Actor-based)。说来惭愧,这几个中文名词我还真没有听过。书主要分五部分,第一部分仍是对并发的概念、应用场合等基础方面进行讲解,这部分通俗易懂,并且选用的实例很是具备表明性。后面几部分即JDKK并发模型、STM、Actor-based和后记。今天这部分主要记录前三章的内容,关于并发策略。 多线程
在讲到Race Condition的时候,做者用到了一个因为JIT编译器优化而产生的并发问题,固然这也跟java内存模型相关,可是这样的例子在以前的书确实不多见。书上给出了一个简短的程序: 并发
public class RaceCondition { private static boolean done; public static void main(String[] args) throws InterruptedException { new Thread(new Runnable(){ @Override public void run() { int i=0; while(!done){ i++; } System.out.println("Done!"); } }).start(); System.out.println("OS:"+System.getProperty("os.name")); Thread.sleep(2000); done=true; System.out.println("flag done set to true"); } }这个程序在JVM server模式下和client模式下运行的结果会不一样,在client模式下,程序能够执行到Done,而server模式则不行。做者用的是JDK1.6的环境,很惋惜我在1.7版本的JDK上并无成功的运行处相应的结果。固然个人机器是64位的,书中给出的是32位的机器。
咱们暂且运用做者给出的结论,这是因为server JIT编译器优化所致使的。JIT编译器可能对新线程代码里的while循环进行优化,致使新线程在线程上下文中不能看到变量done的变化。还有可能会是新线程只会从寄存器或本地cache中读取done的值。这均可以归结为内存栅栏问题,具体可看wiki百科,有很详细的解释。说到这里,是否是发现这个问题和内存可见性有关了,若是能想到这一步,那解决方案其实就很简单了,在done前面加volatile修饰,让done再也不付线程中作cache,内存都去主内存中读取。这多是个挺巧的问题,因为编译器的优化形成了内存可见性的问题。关于内存可见性,在《Java Concurrency in Practice》中有很详细的描述。 ide
接下去的第二章叫分工原则,这里其实将分工分为IO密集型和计算密集型两种。针对这两种分工做者提出了不一样的多线程解决方案。这里讲的多线程解决方案是为了提供一种方法来提升程序运行的效率(和单线程相比),这一章没有讨论多线程可能产生的问题。这一章提供了这么两个案例:第一个是计算某富商的资产净值,第二个则是统计某个区间内素数的个数,容易理解第一个用于讨论IO密集型,剩下的那个用于讨论计算密集型。 工具
这一章会有不少结论性的成果,或者说是做者的经验,这些经验在其余任何一本并发编程的书里都会有,并且每一个人的结论还都不同,因此实际问题实际解决,这里只作参考。在解决上述两个问题的时候,咱们应该有如下两个关键的步骤,第一,肯定线程数,第二,肯定任务数量。关于线程数的肯定,这个说法就不少了,书上给出的结论是: 性能
线程数=CPU可用核心数/(1-阻塞系数),其中阻塞系数取0和1之间。其中CPU的可用核心数很容易拿到: 优化
Runtime.getRuntime().availableProcessors();
计算密集型任务的阻塞系数为0,而IO密集型任务的阻塞系数接近1。在实际问题中若是须要比较精确的评估阻塞系数,能够经过性能分析工具对任务进行跟踪。在第二步,肯定任务数量上,这个也没有一个明确的指示,有时候并不必定是任务书等于线程数,针对IO密集型任务,因为每一个任务的代价基本相同,因此每一个任务分配一个线程仍是比较合理的,可是在计算素数的个数任务中,因为数的特性,判断素数所用的时间不一样的数会有不一样的效率,因此不能很简单的将数分几段,而后分配几个线程去执行。书上还给出了从一个顺序执行的程序改写成并发程序的代码,采用的是线程池进行处理。固然你们也能够试着去写写用多线程的方式计算一个区间内的素数。很简单,都是入门级的代码,尽可能采用concurrent包里的工具,这些会更简单。 google
第三章主要讲述了三种能够处理状态的方法,shared mutability、isolated mutability、pure immutability。这里的状态值得是并发编程中常常提的是否具备可变性的状态。咱们知道,一个类要是状态是不可变的,那天然就不存在同步的问题。可是不多咱们能够写出一个不改变状态的类。因此这一章就是经过这三种方法的介绍来给出解决这一问题的方案。我只提供术语,有兴趣的均可以去google里搜索,都有相应的结论。 spa