Java 并发编程之美学习笔记之基础篇

引言

upload successful

多线程或并发编程是Java体系中的重点和难点,新购入 《Java 并发编程之美》,本博以笔记的形式记录知识要点。


并发编程线程基础

进程和线程关系

一个进程中有多个线程,多个线程共享进程的堆和方法区资源,但是每个线程有自己的程序计数器和栈区域。

程序计数器用来记录线程当前要执行的指令地址。

每个线程都有自己的栈资源,用于存储该线程的局部变量,这些局部变量是该线程私有的,其他线程是访问不了的,除此之外栈还用来存放线程的调用栈帧。

堆里面主要存放使用 new 操作创建的对象实例。

方法区用来存放 JVM 加载的类、常量及静态变量等信息,也是线程共享的。

线程的创建与运行

Java 中有三种线程创建方式,分别为实现 Runnable 接口的 run 方法,继承 Thread 类并重写 run 的方法,使用 FutureTask 方式。

使用继承方式的好处是:在 run 方法内获取当前线程直接使用 this 就可以了,无须使用 Thread.currrentThread() 方法。不好的地方是 Java 不支持多继承。另外任务与代码没有分离,当多个线程执行一样的任务时需要多份任务代码。而 Runnable 则没有这个限制。Runnable 只能使用主线程里面被声明为 final 的变量。

上面两种方式都有一个缺点,就是任务没有返回值。Futuretask 方式可以拿到返回值。

线程通知与等待

当一个线程调用一个共享变量的 wait() 方法时,该调用线程会被阻塞挂起,直到发生下面几件事情之一才返回

  • 其他线程调用了该共享对象的 notify() 或 notifyAll() 方法。
  • 其他线程调用了该线程的 interrupt()方法。
  • 该线程抛出 InterruptedException 异常返回。

Note: 这里要注意的是区分共享对象线程

另外需要注意的是,如果调用 wait() 方法时没有事先获取该对象的监视器锁,则调用 wait() 方法时调用线程会抛出 IllegalMonitorStateException 异常。

当前线程调用 wait() 方法后只会释放当前共享变量上的锁,如果当前线程还持有其他共享变量的锁,则这些锁是不会被释放的。

让线程睡眠的 sleep 方法

Thread 类中有一个静态的 sleep 方法,当一个执行中的线程调用了这个方法后,调用线程会暂时让出指定时间的执行权,也就是在这期间不参与CPU的调度,但是该线程所拥有的监视器资源,比如锁还是持有不让出的

让出 CPU 执行权的 yield 方法

Thread 类中有一个静态的 yield 方法,当一个线程调用 yield 方法时,当前线程会让出CPU使用权,然后处于就绪状态,线程调度器会从线程就绪队列里获取一个线程优先级最高的线程。

线程死锁

死锁的产生必须具备以下四个条件

  • 互斥条件
  • 请求并持有条件
  • 不可剥夺条件
  • 环路等待条件

造成死锁的原因其实和申请资源的顺序有很大关系,使用资源申请的有序性原则就可以

ThreadLocal

ThreadLocal 是 JDK 包提供的,它提供了线程本地变量,如果你创建了一个 ThreadLocal 变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量时,实际操作的是本地内存里的变量,从而避免了线程安全问题。

ThreadLocal 不支持继承性

InheritableThreadLocal 类继承自 ThreadLocal 类,提供了一个特性,就是让子线程能访问到父线程中设置的本地变量。