Java多线程

概况

本文主要介绍Java中线程的概念及使用、多线程的优势及应用、垃圾收集的原理等

相关概念

多任务

Multitasking同时执行多个任务的过程,可以用于充分利用CPU。

  1. 基于进程的多任务

    • 每个进程在内存中有自己的地址(每个进程分配独立的内存区)
    • 进程是重量级
    • 进程间通信的代价是昂贵
    • 从一个进程切换到另一个进程需要一段时间保存和备份寄存器、内存映射区、更新列表等
  2. 基于线程的多任务

    • 线程共享同一地址空间
    • 线程是轻量级
    • 线程间通信的代价很低

线程

  1. 线程是一个轻量级的子进程,一个最小处理单元
  2. 线程是一个单独的执行路径
  3. 线程是独立的,如果一个线程中发生异常,该线程并不影响其它线程
  4. 线程共享通用内存区
  5. 线程在进程里被执行
  6. 线程间存在上下文切换
  7. 每次只有一个线程被执行

Java多线程

概念

  • Java多线程同时执行多个线程的过程
  • 使用多线程而不是多进程,是因为线程共享通用内存区,它们不需要分配独立的内存区,可以节省内存,同时线程间的上下文切换比进程间更省时

优势

  1. 因为线程是独立的,不会堵塞用户,并且可以同时执行多个操作
  2. 可以同时执行许多操作来节省时间
  3. 线程是独立的,如果一个线程发生异常了,它不会影响其它线程

线程相关点

生命周期(状态)

Java中线程的生命周期由JVM控制,如下图所示

  1. New:如果你创建了一个线程实例,但还未调用start()方法,此时该线程处于新建状态
  2. Runnable:在调用start()方法后线程处于可运行状态,但是线程调度器还未选中该线程作为运行时线程
  3. Running:如果线程调度器选中个一个线程,则该线程处于运行时状态
  4. Non-Runnable(Blocked):当线程还存活时该线程处于不可运行(堵塞)状态,但是该线程当前不能运行
  5. Terminated:当线程的run()方法存在时该线程处于终止/死亡状态

create()

创建线程,有以下两种方式

继承Thread类

  • 线程类提供了构造函数方法来创建和执行线程操作
  • 线程类继承Object类,实现Runnable接口

实现Runnable接口

  • Runnable接口应该由想要被线程执行的类实现
  • Runnable接口只有一个run()方法

start()

启动一个新建的线程,执行以下任务

  • 一个新建的线程启动(包括调用堆栈
  • 线程从New状态变换到Runnable状态
  • 当线程得到机会执行时,它的run()方法会执行

启动线程两次

  • 一个线程启动后,不会再次被启动
  • 如果再次启动同一线程,会抛出IllegalThreadStateException在这种情况下,线程会运行一次,但在几秒之后会抛出异常

调用run()方法

  • 每个线程在单独的调用堆栈里启动
  • 从主线程调用run()方法,会进入到当前调用堆栈而不是新建调用堆栈

sleep()

用于休眠一个进程指定的时间。线程提供两种方法休眠一个进程

  • public static void sleep(long miliseconds) throws InterruptedException
  • public static void sleep(long miliseconds, int nanos) throws InterruptedException

join()

等待一个线程死亡。换句话说,join()方法引起当前运行着的线程停止执行知道联合的线程完成任务

命名线程

线程提供方法修改和获取线程的名字

  • public String getName():用于返回一个线程的名字
  • public void setName(String name):用于修改一个线程的名字

currentThread()

返回当前执行线程对象的引用

线程优先级

概念

  • 每个线程有一个优先级
  • 优先级可以使用1~10的数字来描述
  • 大多数情况下线程调度器根据优先级抢占式调度)来调度线程,但是不能保证因为依赖于JVM的规范选择哪种调度方式

线程定义的3个常量

  • public static int MIN_PRIORITY
  • public static int NORM_PRIORITY
  • public static int MAX_PRIORITY

    一个线程的默认优先级是5(NORM_PRIORITY),MIN_PRIORITY的值为1,MAX_PRIORITY的值为10

守护线程

概念

  • Java中的守护线程是一个提供服务线程,可以为用户线程提供服务
  • 它的生命周期依赖于用户线程,当所有的用户线程都死亡后,JVM自动终止这个线程
  • 有一些自动运行着的守护线程,如finalizer。

特征

  • 支持后台任务,提供服务给用户线程,其生命周期中只有服务用户线程的角色
  • 生命周期依赖于用户线程
  • 低优先级的线程

若没有用户线程,JVM终止守护线程

守护线程的单独目的是为支持后台任务,提供服务给用户线程。如果没有用户线程了,JVM为何要一直运行此线程呢?

线程调度器

  • 线程是JVM的一部分,用于决定哪个线程应该运行
  • 没有保证哪个可运行的线程被线程调度器选中运行
  • 在一个单独的进程中,一次只要一个线程可以运行
  • 线程调度器主要使用抢占式调度时间分片调度来调度线程

    Note:抢占式调度与时间分片调度的区别

  • 抢占式调度中,高优先级的任务执行,直到进入等待或死亡状态
  • 时间分片中,一个任务执行一段预定时间片,然后重新进入准备任务池,调度器然后基于优先级和其它因素来决定接下来哪个任务应该执行

线程池

概念

  • 线程池代表一组等待多次工作和重用的工作线程
  • 在线程池的情况下,一组固定大小的线程被创建
  • 一个线程从线程池中被取出来,被服务提供者分配一个工作
  • 在工作完成后,线程再次保留在线程池中

优势

节省时间,因没有必要创建新线程

线程组

  • Java提供了一种方式在一个单独的对象里组合多个线程,在这种情况下,可以通过一个单独的方法调用来实现一组线程的挂起、重启或中断
  • Java线程组被java.lang.ThreadGroup类实现

Java Shutdown Hook

概念

  • shutdown hook用于当JVM正常或异常关闭时执行清理资源或保存状态
  • 执行清理资源意味着关闭日志文件、发送警告或其它
  • 在JVM关闭前若想要执行一些代码,使用shutdown hook

JVM什么时候使用shutdown hook?

  • 用户按下Ctrl+C
  • 调用System.exit(int)方法
  • 用户注销
  • 用户关闭

addShutdownHook(THread hook)

Runtime类的addShutdownHook()方法用于注册虚拟机的线程Runtime类的对象可以通过调用静态工厂方法getRuntime()来获取。

执行多任务

  • 使用多线程执行单独的任务:如果需要被许多线程执行单独的任务,只需要拥有一个run()方法
  • 使用多线程执行多个任务:如果需要被许多线程执行多个任务,需要拥有多个run()方法

垃圾收集

概念

  • Java中垃圾意味着不被引用的对象
  • 垃圾收集是一种自动回收运行时未使用内存的机制,换句话说,是一种销毁不再使用对象的方式
  • C中使用free()C++中使用delete()来收集垃圾。但Java中自动执行,所有Java提供了更好的内存管理

优势

  • 让Java内存高效,因为垃圾收集从堆内存里移除了不被引用的对象
  • 由垃圾收集自动完成,不需要做额外功

对象可以如何被未引用?

  • 置空引用
  • 把一个引用赋值给另一个引用
  • 匿名类

finalize()

  • 在对象被垃圾回收之前,finalize()方法每次都会被调用
  • 该方法可以执行清除操作
  • 该方法在Object类里被定义
  • JVM的垃圾收集器只回收使用new关键字创建的对象。所以如果没有使用new创建对象,可以使用finalize()方法执行清除操作

gc()

  • 用于调用垃圾回收器执行清除操作
  • 该方法在SystemRuntime类里被定义里
  • 垃圾收集被一个叫GC (Garbage Collection)的守护线程执行。守护线程在对象被垃圾回收之前调用finalize()方法

Runtime

  • Runtime类用于和java运行时环境交互
  • Runtime类提供方法执行操作,如调用GC、获取全部和剩余内存等
  • 对于一个Java应用来说,只有一个java.lang.Runtime类实例
  • Runtime.getRuntime()方法返回Runtime类的实例

exec()

在一个单独的进程里执行被提供的命令

freeMemory()

返回JVM里剩余内存

totalMemory()

返回JVM里全部内存

参考方案

Multithreading in Java