13|并发理论基础再梳理

并发的故事起源是怎么样的呢?

起源是一个硬件的核心矛盾:CPU 与内存、I/O 的速度差异,系统软件(操作系统、编译器)在解决这个核心矛盾的同时,引入了可见性、原子性和有序性问题,这三个问题就是很多并发程序的 Bug 之源。

那如何解决这三个问题呢?Java 语言自然有招儿,它提供了 Java 内存模型和互斥锁方案。所以,在02我们介绍了 Java 内存模型,以应对可见性和有序性问题;那另一个原子性问题该如何解决?多方考量用好互斥锁才是关键,这就是03和04的内容。

虽说互斥锁是解决并发问题的核心工具,但它也可能会带来死锁问题,所以05就介绍了死锁的产生原因以及解决方案;同时还引出一个线程间协作的问题,这也就引出了06这篇文章的内容,介绍线程间的协作机制:等待 - 通知。

你应该也看出来了,前六篇文章,我们更多地是站在微观的角度看待并发问题。而07则是换一个角度,站在宏观的角度重新审视并发编程相关的概念和理论,同时也是对前六篇文章的查漏补缺。

08介绍的管程,是 Java 并发编程技术的基础,是解决并发问题的万能钥匙。并发编程里两大核心问题——互斥和同步,都是可以由管程来解决的。所以,学好管程,就相当于掌握了一把并发编程的万能钥匙。

而后在09、10和11我们又介绍了线程相关的知识,毕竟 Java 并发编程是要靠多线程来实现的,所以有针对性地学习这部分知识也是很有必要的,包括线程的生命周期、如何计算合适的线程数以及线程内部是如何执行的。

最后,在12我们还介绍了如何用面向对象思想写好并发程序,因为在 Java 语言里,面向对象思想能够让并发编程变得更简单。

img

经过这样一个简要的总结,相信你此时对于并发编程相关的概念、理论、产生的背景以及它们背后的关系已经都有了一个相对全面的认识。至于更深刻的认识和应用体验,还是需要你“钻进去,看本质”,加深对技术本身的认识,拓展知识深度和广度。

竞态条件需要格外关注

《07 | 安全性、活跃性以及性能问题》里的思考题是一种典型的竞态条件问题(如下所示)。竞态条件问题非常容易被忽略,contains() 和 add() 方法虽然都是线程安全的,但是组合在一起却不是线程安全的。所以你的程序里如果存在类似的组合操作,一定要小心。


void addIfNotExist(Vector v, 
    Object o){
  if(!v.contains(o)) {
    v.add(o);
  }
}

这道思考题的解决方法,可以参考《12 | 如何用面向对象思想写好并发程序?》,你需要将共享变量 v 封装在对象的内部,而后控制并发访问的路径,这样就能有效防止对 Vector v 变量的滥用,从而导致并发问题。你可以参考下面的示例代码来加深理解。



class SafeVector{
  private Vector v; 
  // 所有公共方法增加同步控制
  synchronized 
  void addIfNotExist(Object o){
    if(!v.contains(o)) {
      v.add(o);
    }
  }
}

InterruptedException 异常处理需小心

《 09 | Java 线程(上):Java 线程的生命周期》的思考题主要是希望你能够注意 InterruptedException 的处理方式。当你调用 Java 对象的 wait() 方法或者线程的 sleep() 方法时,需要捕获并处理 InterruptedException 异常,在思考题里面(如下所示),本意是通过 isInterrupted() 检查线程是否被中断了,如果中断了就退出 while 循环。当其他线程通过调用th.interrupt().来中断 th 线程时,会设置 th 线程的中断标志位,从而使th.isInterrupted()返回 true,这样就能退出 while 循环了。



Thread th = Thread.currentThread();
while(true) {
  if(th.isInterrupted()) {
    break;
  }
  // 省略业务代码无数
  try {
    Thread.sleep(100);
  }catch (InterruptedException e){
    e.printStackTrace();
  }
}

这看上去一点问题没有,实际上却是几乎起不了作用。原因是这段代码在执行的时候,大部分时间都是阻塞在 sleep(100) 上,当其他线程通过调用th.interrupt().来中断 th 线程时,大概率地会触发 InterruptedException 异常,在触发 InterruptedException 异常的同时,JVM 会同时把线程的中断标志位清除,所以这个时候th.isInterrupted()返回的是 false。正确的处理方式应该是捕获异常之后重新设置中断标志位,也就是下面这样:



try {
  Thread.sleep(100);
}catch(InterruptedException e){
  // 重新设置中断标志位
  th.interrupt();
}

你可能感兴趣的:(13|并发理论基础再梳理)