进程、线程

进程是程序向操作系统申请资源的基本单位。线程是进程中可独立执行的最小单位。

一个进程可以包含多个线程,同一个进程中的所有线程共享该进程的资源,例如内存、文件句柄。

例如:进程是一个饭店,线程饭店里的员工,员工们可以共享饭店里的一些资源,员工们同时为不同的客户服务,就是多线程运行。

使用多线程的原因

对于单核 cpu,多线程也是必要的,当遇到阻塞性(例如 IO)的操作时,可以单独开一个线程去处理,以防止主线程阻塞,失去响应。

对于多核 cpu,多线程可以充分利用多核的资源,让多个核同时运行。

Java 线程 API

标准库类 java.lang.Thread 就是 Java 平台对线程的实现,一个 Thread 对象(或实例)就是一个线程。

线程的任务处理逻辑可以在 Thread 类的 run 方法中直接实现,run 方法是线程任务的入口。它由虚拟机运行该线程是去调用,而不是应用代码去调用。

所以运行一个线程实际上就是让 Java 虚拟机去执行该线程的 run 方法。为此我们首先要启动线程,Thread 类的 start 方法就是用于启动线程,启动线程的实质是请求虚拟机运行相应线程的 run 方法,但是这个线程何时能运行是由线程调度器决定的。

Thread 类有两种构造器:Thread()和 Thread(Runnable target)。第一种需要一个子类继承 Thread 类,并重写 run 方法。第二种创建一个 java.lang.Runnable 接口实例,并在该实例的 run 方法中实现任务的逻辑,然后将该 Runnable 接口实例作为构造器的参数直接 new 一个 Thread 类的对象。

run 方法执行结束或因为异常而中止后,线程占用的资源会像其他 Java 对象一样被 GC 回收。

线程是一次性用品,一个 Thread 对象只能 start 一次。

创建线程两种方式的区别:第一种是继承,第二种是组合,一般我们认为组合的耦合性更低,是优先选择的技术。但是第二种方式意味着多个线程实例可以共享一个 Runnable 实例,在某些情况下可能出现问题。

线程属性

  • ID:不同的线程拥有一个唯一的编号
  • Name:用户可以设定一个线程的 name,方便人识别线程。
  • Daemon:负责一些关键人物的线程不适合设置为守护线程
  • Priority:优先级,类型 int,范围 1-10,默认 5,子线程优先级和父类相同。一般默认即可,不恰当的设置可能导致问题(线程饥饿)

线程的层次关系

Java 平台的线程不是孤立的,线程与线程之间总是存在一些联系。main 线程创建了线程 A,那么 main 线程就是 A 线程的父线程,A 线程还能继续创建线程,所以线程之间的关系是一颗树。

Java 平台没有 Api 用于获取父线程或获取子线程,他们独立运行,父线程运行结束不会影响子线程,反之亦然。

线程的生命周期

Thread-1

  • new:一个刚创建还未启动的线程处于该状态,并且只可能有一次处于该状态
  • runnable:该状态可以看成一个复合状态,它包含两个子状态:ready 和 running,ready 表示可以被线程调度器调度到 running 状态,running 表示正在运行,执行 Thread.yield()状态可能会从 running 转为 ready
  • blocked:线程发起阻塞式 I/O 操作或申请锁时处于该状态,待 I/O 完成后,线程转为 runnable 状态
  • waiting:一个线程执行了某些特定方法后处于等待其他线程执行特定操作的状态。使线程变为 waiting 的方法包含:Object.wait(),Thread.join()和 LockSupport.park(Object),使 waiting 变为 runnable 的方法包含 Object.notify()/notifyAll()和 LockSupport.unpark(Object)
  • time_waiting:该状态和 waiting 类似,差别在于线程有自身带有时间状态,时间过后自动变为 runnable
  • terminated:已经执行结束的线程处于该状态,一个线程只可能有一次处于该状态