[关闭]
@CLSChen 2019-10-03T16:31:02.000000Z 字数 4802 阅读 810

Java并发编程实战

未分类


第一章 简介

最开始的计算机只能运行一个程序,操作系统的出现带来了进程,提高了资源利用率,公平性和便利性。而线程则进一步细分了进程。

  1. 计算机 -> 操作系统(进程) -> 线程
  2. 1 n n*m

但凡做事高效的人,总能在串行性和异步性之间找到合理的平衡,对于程序也是如此。

线程共享进程的变量等资源,如果没有明确的协同机制,那么当一个线程正在使用某个变量时,另一个线程可能同时访问这个变量,这将造成不可预测的结果。

线程的优势

线程的风险

线程无处不在

框架中可能也会创建线程,JVM启动时,它将为JVM的内部任务,垃圾收集器,终结操作等创建后台线程,并创建一个主线程来运行main方法。

第二章 线程安全性

当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。

原子性 Atomic

一个无状态类,只是加了一个count值,在每次调用这个类的时候count++,它就不是线程安全的了:count++并非一个原子操作,它包含了读取修改写入三个操作,每次操作都依赖于前面的状态。
在并发编程中,由于不恰当的执行时序而出现不正确的结果是一种非常重要的情况,被称为竞态条件

  1. public class LazyInitRace{
  2. private ExpensiveObject instance = null;
  3. public ExpensiveObjcet getInstance(){
  4. if(instance == null) // 如果两个线程同时检查怎么办?
  5. instance = new ExpensiveObject();
  6. return instance;
  7. }
  8. }
  9. 如果两个线程同时执行getInstance方法,而且都觉得还未创建,又分别都创建了,那可能会得到两个instance
  10. 这可不是我们想要的“单例模式”

加锁机制

光使用线程安全类是不够的,当更新某个变量时,需要在同一个原子操作中对其他变量同时进行更新。

用锁来保护状态

当获取与对象关联的锁时,并不能组织其他线程访问该对象,只能阻止其他线程获得同一个锁,每个对象都有一个内置锁只是为了免去显式地创建锁对象。

一种常见的加锁约定是,将所有的可变状态都封装在对象内部,并通过对象的内置锁对所有访问可变状态的代码路径进行同步。在许多线程安全类中都是用了这种模式,如Vector和其他的同步集合类。

如果不加区别的滥用synchronized,可能会导致更多的同步。

  1. if(!vector.contains(element))
  2. vector.add(element);
  3. 虽然containsadd都是原子方法,但是这个操作中仍然存在竞态条件。

活跃性与性能

我们应该缩小同步代码块的作用范围,但是不要太小,不要将本应该是原子的操作拆分到多个同步代码块中。尽量将不影响共享状态且执行时间较长的操作从同步代码块中分离出去。

当执行时间较长的计算或者可能无法完成的操作时(网络IO操作,控制台IO操作),一定不要持有锁。

第三章 对象的共享

第二章强调了原子性,即不能有两个线程修改同一个变量。而同步还有另一个重要的方面:可见性。我们希望当一个确保一个线程修改了对象状态后,其他线程能够看到发生的状态变化。

  1. public class NoVisibility{
  2. private static boolean ready;
  3. private static int number;
  4. private static class ReaderThread extends Thread{
  5. public void run(){
  6. while(!ready) // ready在被修改为true前线程阻塞
  7. Thread.yield();
  8. System.out.println(number);
  9. }
  10. }
  11. public static void main(String[] args){
  12. new ReaderThread().start();
  13. number = 42;
  14. ready = true;
  15. }
  16. }

程序有这么几种情况:
1.一直循环下去,主线程的两个修改一个都没看见。
2.输出0,只有ready的修改被看见了。因为重排序的原因,ready的修改在number之前。
3.输出42,两个都被看见了。

这三种情况是哪一种我们并无法知道,这就是非同步带来的可见性问题。

加锁与可见性

加锁的含义不仅仅局限于互斥行为,还包括内存可见性。为了确保所有线程都能看到共享变量的最新值,所有执行操作的线程都必须在同一个锁上同步。

发布与逸出

发布就是使对象能够在当前作用域之外的代码中使用。
逸出就是指某个不该发布的对象被发布。

线程封闭

如果仅在单线程中访问数据,就不需要同步,这种技术被称为线程封闭(Thread Confinement)。这是实现线程安全性的最简单方式之一。
在Swing和JDBC中大量使用了线程封闭技术。

不变性

安全发布

对象的组合

我们希望能用一些现有的线程安全组件组合起来,组合为更大规模的组件或程序,这些模式能够使得一个类更容易成为线程安全的,并且维护这些类时不会无意中破坏类的安全性保证。

如果对象的所有域都是基本类型的变量,这些域就是对象的全部状态。
如果对象的域引用了其他对象,那么该对象的状态将包含被引用对象的域。比如LinkedList的状态就包括该链表中所有节点对象的状态。

基础构建模块

同步容器类

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注