定义
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,同一进程中的多条线程将共享该进程中的全部系统资源。线程是独立调度和分派的基本单位。 – 百度百科
创建一个线程
继承Thread类
Thread类代表一个线程实例,启动线程的唯一方法是通过Thread类是start()实例方法[注1],start()方法是一个native方法,它将启动一个新的线程,并执行run()方法:
public class CreateThread {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start(); // 调用start()启动一个线程。 注意不是run()方法
}
}
class MyThread extends Thread { // 定义自己的线程实现:
@Override
public void run() { // run方法内为线程的逻辑
System.out.println("extends Thread运行");
}
}
实现Runnable接口
Thread类似实质上是Runnable接口的一个实现类,因为Java的单继承特性,如果一个类已经extends了其他类,就不能再通过extends Thread来定义一个线程,此时可以通过实现Runnable接口来定义一个线程,但是线程依然需要Thread的start()方法启动:
public class CreateThread {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread rt = new Thread(mr); // 将实现了Runnable的类作为Thread的构造参数
rt.start();// 调用start()启动一个线程
}
}
class MyRunnable implements Runnable { // 定义自己的线程类并实现run方法
@Override
public void run() {
System.out.println("implements Runnable运行");
}
}
注1: 线程的启动时调用Thread的start()方法而非run()方法。Thread的start()方法启动一个线程,这个线程处于就绪状态,并在获得CPU之后运行执行run()方法。run()方法被称为线程体,即线程实际的运行逻辑,如果直接调用run()方法,那么就仅仅是一次普通的方法调用,而非是启动一个线程。
线程的生命周期
当线程被创建之后,并不是立即就进入执行状态,进入执行状态后也不一定能一直霸占着CPU一直执行到死亡。线程的生命周期包含创建,就绪,运行,阻塞,死亡五个阶段。
新建: 当一个Thread实例被new出来时,一个线程就处于创建状态了,此时它和一个普通的JVM对象一样存在于内存中。
就绪: 当程序调用了Thread实例的start()方法之后,线程就由创建状态进入到就绪状态,此时的线程等待操作系统分配CPU的执行时间给它。
运行: 一旦线程获得CPU,那么就会进入运行状态,此时线程会执行run()方法内的逻辑:线程体。
阻塞: 当线程因为某些原因需要暂时停止运行放弃CPU时就进入了阻塞状态。进入阻塞状态可能是因为线程调用了wait()方法,或者是线程获取同步锁时锁已经被其他线程占用。
死亡: 线程因为某些原因结束运行,就进入了死亡状态。造成死亡状态的原因可能有:正常运行结束,运行中出现未捕获的异常并抛到线程体外,调用了线程的stop()方法强制中断线程。
Thread的基本方法
- yield():静态方法,执行yield方法时当前线程会让出CPU的执行时间片,由所有线程重新竞争CPU。
- interrupt():实例方法,其作用是将实例线程的中断位设置成true,并不是直接中断实例线程。
- interrupted():静态方法,其作用是返回当前线程的中断位,并重置当前线程的中断位为false
- isInterrupted():实例方法,其作用是返回实例线程的中断位,但并不会重置中断位。
- isAlive():实例方法,作用是返回实例线程是否存活。
- activeCount():静态方法,作用是返回当前程序中活跃的线程数目。
- enumerate(Thread tarray[]):静态方法,作用是将当前程序中所有的活跃线程复制到指定数组中。
- currentThread():静态方法,作用是获得当前线程。
- isDaemon():实例方法,作用是返回实例线程是否是一个守护线程[注2]。
setDaemon(boolean on):实例方法,作用是指定实例线程是否作为一个守护线程。 - setName(String name):实例方法,对实例线程进行命名。
getName():实例方法,获得实例线程的名称。 - setPriority(int newPriority):实例方法,设置实例线程的优先级[注3]。
getPriority():实例方法,获得实例线程的优先级。 - sleep(long millis):静态方法,线程睡眠一段时间,不会释放锁。sleep和wait的区别
注2: 见下文
注3: 优先级高的线程并不是一定比优先级低的线程先获得CPU,只是获得CPU的概率大一些
线程上下文切换
上下文切换巧妙的利用了时间片轮转的方式,CPU给每个任务都服务一段时间,然后把当前任务的状态保存下来,在加载下一个任务的状态后,继续服务下一个任务。任务的状态保存以及再加载,这段过程就叫做上下文切换。时间片轮转方式使得多个任务可以在同一颗CPU上执行。
上下文就是指某一个时间点CPU寄存器和程序计数器的内容。
上下文切换的活动:
- 挂起一个任务,将任务在CPU中的状态(上下文)存储到内存中。
- 在内存中检索下一个任务的上下文并将其在CPU寄存器中恢复。
- 跳转到程序计数器指定的位置,恢复该任务的执行。
引起上下文切换的原因:
- 当前任务的时间片使用完后,系统CPU正常调度下一个任务
- 当前任务执行到IO阻塞,调度器将当前任务挂起,继续下一个任务
- 多个任务抢占锁资源,当前任务没有抢到锁资源,调度器将当前任务挂起,继续下一个任务
- 代码主动挂起当前任务,放弃CPU
- 硬件中断
守护线程
- 守护线程,也称为服务线程或后台线程,守护线程即为用户线程提供服务的线程
- 我们创建的线程默认为用户线程。但是可以通过setDaemon(true)将其设置为守护线程
- 守护线程中创建的线程也是守护线程
- 守护线程拥有较低的优先级
- 守护线程是JVM级别的,并不会随着某个程序的停止而结束。它独立于程序并且周期性的执行某种任务。只有当JVM上所有的用户线程全部停止,守护线程才会停止,之后JVM也就退出了
- GC线程就是一个经典的守护线程。它周期性的在低优先级进行垃圾回收的工作,当所有用户线程全部停止时不会有新垃圾产生,此时GC线程是JVM上仅剩的线程,GC线程就会自动离开
PS:
【JAVA核心知识】系列导航 [持续更新中…]
上篇导航:11: 从源码看ConcurrentHashMap:一文看懂ConcurrentHashMap
下篇导航:13:一文看懂JAVA线程池,轻松应对面试
欢迎关注…