并发系列1 Java并发编程基础

参考:
《Java并发编程的艺术》第四章
《Java多线程编程核心技术》
博客 https://www.jianshu.com/p/8a04b5ec786c Java多线程基础
博客 https://www.jianshu.com/p/12af2d966c13 Java并发编程1

一.线程简介

1.线程和进程
  • 进程是系统进行资源分配和调度的一个独立单位,现代操作系统运行程序时会创建进程
  • 线程也叫轻量级进程,是现代操作系统调度的最小单元,一个进程中可以有多个线程,每个线程都拥有各自的计数器、堆栈和局部变量等属性,并能够访问共享的内存变量
2.为什么使用多线程
  • 更多的计算核心:充分利用多核处理器的硬件优势,将计算逻辑分配给多个处理器同时执行
  • 更快的响应时间:在业务逻辑复杂的场景中,将数据一致性不强的操作派发给其他线程处理
  • 更好的编程逻辑:Java提供了良好并且一致的多线程编程模型,方便开发者完成多线程开发
3.上下文切换
  • CPU切换运行的线程时存储和恢复CPU的过程,使得线程能够从中断点恢复执行
  • 线程上下文切换过程中会记录程序计数器、CPU寄存器状态等
  • 多线程环境中上下文切换会带来一定的性能开销
4.线程优先级
  • 现代操作系统采取分时的形式调度执行的线程
  • 线程在获取操作系统分出的时间片后开始执行,时间片用完后停止执行,等待再次获得时间片
  • 线程优先级决定线程获取CPU时间片的优先级
  • 注意:Java线程优先级在某些操作系统中无效
5.线程的状态
状态名称 说明
NEW 初始状态,线程被构建,但还没有调用start()方法
RUNNABLE 运行状态,包括线程在操作系统中就绪和运行两种情况
BLOCKED 阻塞状态,表示线程阻塞于锁
WAITING 等待状态,进入等待状态的线程需要等待其他线程的特定动作(通知或中断)
TIME_WAITING 超时等待状态,进入超时等待状态的线程可以在指定的时间自行返回
TERMINATED 终止状态,表示当前线程已经执行完毕
线程状态切换
6. Daemon线程
  • 支持型线程,用作程序中后台调度以及支持型工作
  • JVM不存在非Daemon线程时,Daemon线程将自动结束,JVM退出
  • 注意,在JVM退出时,Daemon线程中的finally块可能不会执行

二.线程启动和终止

1.构造线程
  • void init(ThreadGroup g, Runnable target, String name,long stackSize,AccessControlContext acc)方法完成线程构造
  • 新构造的线程对象由其parent线程进行空间分配,继承了parent线程的Daemon、优先级和加载资源的contextClassLoader以及可继承的ThreadLocal,同时获得唯一的线程ID
2.线程的实现
  • 继承Thread类,重写run()方法,Thread类本身实现了Runnable接口
  • 实现Runnable接口,重写run()方法
  • 使用ExecutorService、Callable、Future实现有返回结果的多线程
3.线程中断
  • 一个线程应该自行停止,而非由其他线程强制中断或停止
  • Thread.stop()不保证资源的正确释放、Thread.suspend()暂停时不释放锁容易导致死锁、Thread.resume()等三个方法都已废弃
  • 每个线程均有一个中断标志位,表示是否有其他线程对该线程进行了中断操作
  • 当对一个线程调用interrupt()方法时
    1)如果线程处于等待状态(如sleep、wait、join)时,线程将立即退出等待状态,并抛出一个interruptedException异常,仅此而已
    2)如果线程处于正常活动状态,会将该线程的中断标志设置为true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响
  • 所以interrupt()并不能真正的中断线程,需要被调用的线程进行配合。如果一个线程有被中断的需求,可以这样做
    1)在正常运行任务时,使用isInterrupted()方法经常检查本线程的中断标志位,如果被设置了中断标志就自行停止
    2)线程处于等待状态时,catch到InterruptedException异常后退出线程
  • Thread.interrupted()方法将清除中断标志位,但并不代表线程又恢复,仅代表线程已响应该中断信号然后重置为可再次接收信号的状态
  • 处于等待的线程在调用interrupt()方法后抛出InterruptedException前,JVM将先清除线程的中断标志位
Modifier and Type Method Description
void interrupt() Interrupts this thread
static boolean interrupted() Tests whether the current thread has been interrupted
boolean isInterrupted() Tests whether this thread has been interrupted

三.线程间通信

1.volatile和synchronized
  • Java支持多个线程同时访问共享对象,现代多核处理器为了加速程序运行,每个线程会拥有共享对象的一份拷贝,由此引出内存可见性问题——一个线程看到的变量并一定是最新的
  • volatile:修饰的字段(成员变量),要求程序对该变量的访问必须从共享内存获取,修改必须同步刷新回共享内存,从而保证所有线程对变量访问的可见性
  • synchronized:修饰方法或同步块,确保同一时刻,只有一个线程处于方法或同步块中,保证了线程对变量访问的可见性和排他性
  • 对象、对象的监视器、同步队列和执行线程之间的关系
    1)任意线程对由synchronized保护的object的访问,首先要获得object的监视器Monitor
    2)如果Monitor获取失败,线程进入同步队列SynchronizedQueue,线程为BLOCKED状态
    3)当其他获得锁访问object的线程释放锁,该释放操作唤醒阻塞在同步队列中的线程,使其重新尝试获取object的Monitor
2.等待/通知机制
  • 生产者消费者模式
    1)线程A修改了一个对象的值,线程B在感知变化后进行相应的操作
    2)整个过程开始于线程A(生产者),最终执行于线程B(消费者)
    3)该模式隔离了“做什么”(What)和“怎么做”(How),功能层面实现了解耦
  • 原始办法
    消费者线程不断循环检查信号变量是否变化,伪代码如下,存在程序及时性和资源消耗量的两难
while ( value != desire )  {
    Thread.sleep ( 1000 ) ;
}
doSometing();
  • 等待/通知机制 notify/wait
    1)线程A调用对象O的wait()方法进入等待状态
    2)线程B调用对象O的notify()notifyAll()方法通知线程A
    3)线程A收到通知后从对象O的wait()方法返回,继续执行后续操作

  • 等待/通知机制流程
    1)使用wait()notify()notifyAll()时需要先对调用对象加锁
    2)调用wait()后,线程状态由RUNNING变为WAITING,并将当前线程放置在等待队列
    3)notify()notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()notifyAll()的线程释放锁之后,等待线程才会从wait()返回
    4)notify()/notifyAll()将等待队列中的一个/全部等待线程从等待队列中移到同步队列,被移动的线程状态由WAITING变为BLOCKED
    5)从wait()方法返回(离开同步队列开始运行)的前提是获得了调用对象的锁

    Wait/Notify

  • 等待/通知的经典范式

等待方伪代码
1.获取对象的锁
2.如果条件不满足,则调用对象的wait()方法,被通知后仍要检查条件
3.条件满足则执行对应逻辑
synchronized(对象)  {
      while(条件不满足)  {
            对象.wait();
      }
      对应的处理逻辑
}

通知方伪代码
1.获取对象的锁
2.改变条件
3.通知所有等待在对象上的线程
synchronized(对象)  {
      改变条件
      对象.notifyAll();
}
3.管道输入/输出流
  • 管道IO主要用于线程间数据传输,传输媒介为内存,与文件IO或网络IO不同
  • 主要实现类
    1)管道字节流:PipedOutputStreamPipedInputStream
    2)管道字符流:PipedReaderPipedWriter
PipedReader in = new PipedReader();
PipedWriter out = new PipedWriter();
//必须连接输入流和输出流,否则抛出异常
out.connect(in);
4.Thread.join()
  • 线程A使用thread.join()表示当前线程A等待thread线程终止后才从thread.join()返回
  • join()方法的源代码逻辑结构与等待/通知经典范式一致,即加锁、循环和处理逻辑三步
5.线程变量ThreadLocal
  • 以ThreadLocal对象为键、任意对象为值得存储结构,依附于线程
  • 线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值

那年离别日,只道住桐庐。桐庐人不见,今得广州书

推荐阅读更多精彩内容