《Java核心技术 卷1》读书笔记(2)- 并发

|第12章 并发

12.1 什么是线程

  • 多线程和写多进程的区别?
    • 每个进程拥有一套完成变量,而线程共享数据
  • 如何通过Runnable启动线程?
1
2
3
  Runnable r = () -> { task code };//函数接口方法run()
  Thread t = new Thread(r); //Thread(Runnable target)
  t.start();

12.2 线程状态

  • 线程的状态有哪些?如何获取?
    • New
    • Runnable 可运行:start()被调用后。可以正在运行也可以未在运行。
    • Blocked 阻塞:要获得一个被其他线程占用的锁
    • Waiting:等待某个条件产生的通知
    • Timed Waiting 计时等待:Thread.sleep...
    • Terminated:退出run()
    • 线程状态可通过Thread.getState()获取
  • 有哪些调度模式?
    • 抢占式、协作式(Thread.yield被调用时才失去控制权。相当于宣告暂时离开)
  • 如何终止线程?
    • 无法强制终止。除非调用已废弃的Thread.stop()。通过Thread.interrupt()可请求终止,设置中断标记
  • InterruptedException何时抛出
    • 在阻塞/等待的线程调用interrupt()时。不要抑制该异常的抛出!
  • interrupt()/isInterrupted()有何区别?
    • 前者是静态方法,调用则修改中断标记;后者是实例方法,调用不影响线程状态
  • setDaemon(boolean isDaemon的作用?
    • 在线程开始前设置线程为守护线程(Daemon thread)或用户线程。守护线程允许JVM在程序执行完毕而线程仍在运行时退出。如GC。

12.4 同步

  • 什么是竞态条件(race condition)
    • 两个或两个以上线程同时修改一个对象。
    • 非原子操作的各个步骤穿插执行的风险很高
  • 哪两种机制可以防止并发访问代码块?
    • synchronizedReentrantLock
  • ReentrantLock重入锁的工作机制?
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
  //每个对象都有一个自己的锁
  var myLock = new ReentrantLock();
  myLock.lock();//确保只有一个线程可以通过此处
  try {
    //critical task
    otherMethod();//可调用另一个使用相同锁的方法。锁持有对象+1
    //锁持有对象-1
  } catch (e Exception) {
    //handling
  } finally {
    myLock.unlock(); //必须写在这,抛出异常时也必须解除锁
    //锁完全释放
  }
  • 何时需要condition variable条件对象?怎么用?
    • 当被重入锁锁定的处理进入临界区,但未达到执行条件时,由于此时别的线程无法访问该对象,执行可能进入死锁。此时调用条件对象暂时放弃锁,等别的线程改变状态可能达到执行条件时的通知,再继续使用锁执行余下操作。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  ReentrantLock myLock;
  Condition condition;
  //initialize
  myLock = new ReentrantLock();
  condition = myLock.newCondition();

  //Thread-1
  private void myMethod(){
    myLock.lock();
    try{
      //(3)再次检查条件,为真则重拾锁,继续critical tasks
      while(some condition is false){
        condition.await();//(1)线程进入等待集(wait set)
      }
      //critical tasks
    } finally {
      muLock.unlock();
    }
  }

  //Thread-2
  //change states to meet the condition
  condition.signalAll();//(2)解除等待集中所有线程的阻塞状态
  • synchronized同步方法等价于获取什么锁?
    • 内部对象锁:intrinsicLock
  • synchronized实现条件对象
1
2
3
4
5
6
7
  private synchronized void myMethod(){
    while (some condition is false) {
      wait()
    }
    //critical tasks
    notifyAll();
  }
  • 更应该使用重入锁和同步方法中的哪个?
    • java.util.concurrent最优,synchronized次优
  • 除了调用同步方法,还有哪种方法可以获得对象的锁?
    • 进入同步块:
1
2
  var obj = new Object();
  synchronized (obj) { }//get obj's lock
  • volatile字段的用处?
    • 为实例字段的同步访问提供了一种免锁机制,令编译器插入适当代码,使对该字段的修改对读取该字段的所有线程都可见。
  • 如何使用线程局部变量避免线程间共享数据?
    • 利用ThreadLocal辅助类wrap对象
1
2
  public static final ThreadLocal<SimpleDateFormat> df = ThreadLocal.withInitial(()-> new SimpleDateFormat("yyyy-MM-dd"));
  String dateStamp = dateFormat.get().format(new Date());

12.5 线程安全的集合

  • 阻塞队列有哪3类方法?分别有哪些特征?
    1. 作为多线程控制工具: take,put;从空队列取元素和向满队列放元素时阻塞。
    2. 抛出异常:
    • add:添加一个元素。满队列则抛出 IllegalStateException
    • remove:去除并返回队头元素。空队列则抛出NoSuchElementException
    • element:返回队头元素。空队列则抛出NoSuchElementException
    1. 不抛出异常只返回错误提示:
    • offer:添加一个元素并返回true。满队列则返回false
    • poll:去除并返回队头元素。空队列则返回null
    • peek:返回队头元素。空队列则返回null
  • 阻塞队列的工作流程
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
  BlockingQueue<Object> queue = new ArrayBlockingQueue<>(size);
  //生产者
  Runnable producer = () -> {
    ...
    queue.put(obj); //放
  };
  new Thread(producer).start()

  //消费者
  Runnable consumer = () -> {
    ...
    queue.take(obj); //取
  }
  new Thread(consumer).start();
  • 其余内容先看第9章 集合

12.6 任务和线程池

  • 如何使用执行器生成线程池?
    1. Executor类的静态工厂方法(newFixedThreadPool等)构造线程池(返回ExecutorService)
    2. submit方法将CallableRunnable提交给线程池
    3. 保存返回的Future对象
    4. 不再提交任务时调用shutdown
  • 如何不单个提交任务而使用任务组批量处理?
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
  //构建填充线程组的任务组
  var tasks = new ArrayList<Callable<Object>>();
  for (...) {
    //向线程组添加Callable任务对象
    //Callable要求返回值,这里假设是Object
    Callable<Object> task = () -> {... return Object;};
    tasks.add(task);
  }
  //构建线程池
  ExecutorService executor = Executors.newCachedThreadPool();
  //invokeAll提交任务组中的所有任务,
  //并返回一个保存了结果的Future列表
  List<Future<Object>> results = executor.invokeAll(tasks);
  //获取结果并批量处理
  for (Future<Object> result : results) {
    //第一个get会阻塞直到计算结果可用
    Object answer = result.get();
    ...
  }