zoukankan      html  css  js  c++  java
  • JAVA 技术手册 卷1 第十四章『多线程』 读书摘要

    什么是线程

    • 进程受CPU时间片的轮转调度,进而予人多任务并发的感觉。
    • 线程在更低层次上扩展多任务概念,一个进程通常包含多个线程。
    • 进程各自数据独立,而线程共享数据。
    • 数据独立使进程相互通信变得繁难,共享数据又使线程并发暗藏风险。

    创建线程的两种方式:

    • 创建一个任务,受单独线程调度。
      public class Task implements Runnable {
          public void run() {
              // TODO: 具体的执行 
          }
          public static void main(String[] args) {
              Runnable runnable = new Task();
              Thread t = new Thread(runnable);
              t.start();
          }
      }
      
    • 创建一个Thread类的子类,不推荐该方式。
      public class MyThread extends Thread {
          @Override
          public void run() {
              // TODO: 具体的执行任务
          }
      }
      

    中断线程

    • 可以用interrupt方法请求中止线程,线程的中断标志被置位,线程某些情况下应不时检查该标志
      while (!Thread.currentThread().isInterrupted()) {
          // TODO: 接着做
      }
      
    • 如果线程阻塞(调用sleepwait),被interrupt的线程会抛出InterruptedException,不可中断的阻塞不会响应interrupt
    • 如果已被interrupt的线程调用sleep,将会复位中断标志并抛出InterruptedException
    • Thread有两个非常类似的方法,interruptedisInterruptedinterrupted是静态方法,会复位当前线程的中断标志isInterrupted是实例方法,不改变中断标志

    线程状态

    线程一共含有6种状态,记于java.lang.Thread.State

    1. New
    2. Runnable
    3. Blocked
    4. Waiting
    5. Timed waiting
    6. Terminated

    线程不会一直保持运行。
    抢占式调度系统在分配时间片时赋予线程运行权,时间片用完时剥夺线程运行权。
    选择下一线程时会考虑线程的优先级。

    Blocked、Waiting、Timed waiting:

    • 当前线程试图获取对象锁而不得时,进入Blocked状态。当其他线程释放该锁,且当前线程持有该锁进入非Blocked状态。
    • 当线程等待java.util.concurrent库中LockCondition、调用Object.waitThread.join等方法时,进入Waiting状态。此时当前线程在等待另一线程通知线程调度器。
    • 当线程调用Thread.sleepObject.waitThread.joinLock.tryLockCondition.await等待时间参数的超期方法,进入Timed waiting状态。
    • 当一个线程被重新激活时,线程调度器会检查其优先级是否比已在运行的线程优先级高,若是,则挑一个线程剥夺其运行权,选择被激活线程运行。

    线程退出

    • run方法退出而线程退出
    • 因未捕获异常抛出而线程退出

    线程属性

    优先级

    • 每个线程都有优先级。
    • 默认情况下,每个线程继承父线程的优先级。
    • 可以调用Thread.setPriority设置线程的优先级
    • JVM的线程优先级高度依赖于系统,Java线程的优先级被映射到宿主平台的优先级上。
    • Windows上有7个优先级,Java线程优先级会出现多对一的情况。
    • Linux上线程不具有优先级。

    守护线程

    • 线程启动前调用Thread.setDaemon(true)将线程转换为守护线程
    • 当只剩下守护线程时,JVM退出

    Unchecked Exception处理器

    • 调用Thread.setUncaughtExceptionHandler为线程安装异常处理器
    • 调用静态Thread.setDefaultUncaughtExceptionHandler为所有线程安装默认异常处理器
    • 如果不为线程安装异常处理器,默认处理器为该线程的ThreadGroup对象
    • ThreadGroupuncaughtException的处理逻辑如下:
      • ThreadGroup
      • DefaultUncaughtExceptionHandler
      • Throwable不是ThreadDeath实例,线程名字以及Throwable的栈信息输出到System.err
    • 使用Unchecked Exception处理器目的在于健壮线程异常退出的记录,如需恢复当前任务只能新建线程重新执行

    线程同步

    java.util.concurrent.locks.ReentrantLock

    • 示例代码
      private Lock lock = new ReentrantLock();
      
      public void doSomething() {
          lock.lock();
          try {
              // do something
          } finally {
              lock.unlock();
          }
      }
      
    • ReentrantLock 可重入,锁内部有计数机制
    • 构造器ReentrantLock(boolean fair)构建公平策略锁,以牺牲性能的代价偏爱等待时间最长的线程。无法保证线程调度器的绝对公平。

    java.util.concurrent.locks.Condition

    • 一个锁可以含有一个或多个条件对象
    • 调用java.util.concurrent.locks.Lock#newCondition获得一个条件对象
    • 线程在持有锁的情况下调用java.util.concurrent.locks.Condition#await()放弃锁并阻塞在Condition的等待集上
    • 另一线程java.util.concurrent.locks.Condition#signalAll激活因条件对象阻塞的线程,这些线程从等待集中移出,再次接受线程调度器的调度,试图重入锁对象。一旦获得锁对象,将从await调用地方返回并继续执行
    • 示例代码
      while(!(ok to proceed))
          condition.await();
      

    synchronized

    • 每个JAVA对象都有一个内部锁
    • 对象内部锁只有一个Condition
    • 对象内部锁的waitnotifyAll,等价于ConditionawaitsignalAll
    • synchronized方法锁对象
    • synchronized静态方法锁对象的类

    Volatile

    • 不同线程访问同一内存地址可能获得不同的值,因为寄存器或本地缓冲区的存在
    • 编译器可以改变指令执行的顺序而不改变代码的语义
    • 使用锁不必担心数据访问不一致,锁保护的临界区不能重排序指令,必要时刷新本地缓存
    • 将域声明为volatile保证数据访问一致性,但不保证数据修改的原子性

    final

    • final修饰的域保证多线程下数据访问一致性

    Atomic

    • java.util.concurrent.atomic下提供了诸如AtomicIntegerAtomicReference等原子类
    • Atomic既保证数据访问一致性,同时保证了数据修改的原子性

    ThreadLocal

    • 每个线程拿到的变量值都是特属于自己的副本,故没有同步的代价
    • java.util.Random是线程安全的,为提高性能JAVA 7引入了java.util.concurrent.ThreadLocalRandom
    • java.text.SimpleDateFormat是非线程安全的,解决该问题为每一个线程构造一个实例,示例代码如下
      public static final ThreadLocal<SimpleDateFormat> dateFormat = new ThreadLocal<SimpleDateFormat>() {
          @Override
          protected SimpleDateFormat initialValue() {
              return new SimpleDateFormat("yyyy-MM-dd");
          }
      };
      

    Lock's wait and interrupt

    • java.util.concurrent.locks.Lock#lock不能被中断,如果线程在等待获得锁时被中断,被中断线程在获得锁之前一直处于阻塞状态
    • java.util.concurrent.locks.Lock#tryLock(long, java.util.concurrent.TimeUnit)可以被中断,中断将抛出InterruptedException
    • java.util.concurrent.locks.Condition#await(long, java.util.concurrent.TimeUnit)tryLock类似

    write/read Lock

    • java.util.concurrent.locks.ReentrantReadWriteLock读并发,写互斥
    • readLock获取读锁,多个读操作可重入,排斥写锁。
    • writeLock获取写锁,排斥其他所有锁。

    阻塞队列

    队列方法

    • add 添加一个元素,如果队列满则抛错
    • element 返回队列的头元素,如果队列空则抛错
    • offer 添加一个元素返回true,如果队列满则返回false
    • peek 返回队列头元素,如果队列空,返回null
    • poll 移出并返回队列头元素,如果队列空,返回null
    • put 添加一个元素,如果队列满则阻塞
    • remove 移出并返回队列头元素,如果空则抛错
    • take 移出并返回队列头元素,如果空则阻塞

    七种队列

    • ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
    • LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
    • PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
    • DelayQueue:一个使用优先级队列实现的无界阻塞队列。
    • SynchronousQueue:一个不存储元素的阻塞队列。
    • LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
    • LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

    参考资料

  • 相关阅读:
    linux之awk命令
    HDU 2097 Sky数 进制转换
    HDU 2077 汉诺塔IV
    HDU 2094 产生冠军 dfs加map容器
    HDU 2073 叠框
    HDU 2083 简易版之最短距离
    HDU 2063 过山车 二分匹配
    天梯 1014 装箱问题
    天梯 1214 线段覆盖
    天梯 1098 均分纸牌
  • 原文地址:https://www.cnblogs.com/lewh/p/6108029.html
Copyright © 2011-2022 走看看