概况
本文主要介绍Java中线程的概念及使用、多线程的优势及应用、垃圾收集的原理等
相关概念
多任务
Multitasking
,同时执行多个任务的过程,可以用于充分利用CPU。
基于进程的多任务
- 每个进程在内存中有自己的地址(每个进程分配独立的内存区)
- 进程是重量级的
- 进程间通信的代价是昂贵的
- 从一个进程切换到另一个进程需要一段时间保存和备份寄存器、内存映射区、更新列表等
基于线程的多任务
- 线程共享同一地址空间
- 线程是轻量级的
- 线程间通信的代价很低
线程
- 线程是一个轻量级的子进程,一个最小处理单元
- 线程是一个单独的执行路径
- 线程是独立的,如果一个线程中发生异常,该线程并不影响其它线程
- 线程共享通用内存区
- 线程在进程里被执行
- 线程间存在上下文切换
- 每次只有一个线程被执行
Java多线程
概念
Java多线程
是同时执行多个线程的过程- 使用多线程而不是多进程,是因为线程共享通用内存区,它们不需要分配独立的内存区,可以节省内存,同时线程间的上下文切换比进程间更省时。
优势
- 因为线程是独立的,不会堵塞用户,并且可以同时执行多个操作
- 可以同时执行许多操作来节省时间
- 线程是独立的,如果一个线程发生异常了,它不会影响其它线程
线程相关点
生命周期(状态)
Java中线程的生命周期由JVM
控制,如下图所示
- New:如果你创建了一个线程实例,但还未调用
start()
方法,此时该线程处于新建状态 - Runnable:在调用
start()
方法后线程处于可运行状态,但是线程调度器还未选中该线程作为运行时线程 - Running:如果线程调度器选中个一个线程,则该线程处于运行时状态
- Non-Runnable(Blocked):当线程还存活时该线程处于不可运行(堵塞)状态,但是该线程当前不能运行
- 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()
- 用于调用垃圾回收器执行清除操作
- 该方法在
System
和Runtime
类里被定义里 - 垃圾收集被一个叫
GC (Garbage Collection)
的守护线程执行。守护线程在对象被垃圾回收之前调用finalize()
方法
Runtime
Runtime
类用于和java运行时环境
交互Runtime
类提供方法执行操作,如调用GC
、获取全部和剩余内存等- 对于一个Java应用来说,只有一个
java.lang.Runtime
类实例 Runtime.getRuntime()
方法返回Runtime
类的实例
exec()
在一个单独的进程里执行被提供的命令
freeMemory()
返回JVM里剩余内存
totalMemory()
返回JVM里全部内存