一、什么是线程 要理解什么线程,我么得先知道什么是进程。现代操作系统在运行一个程序时,会为其创建一个进程。例如启动eclipse.exe其实就是启动了win系统的一个进程。现代操作系统调度的最小单元就是线程,也叫轻量级进程,在一个进程里面包含多个线程,这些线程都有各自的计数器、堆栈等,并且能够共享内存变量。例如我们启动了一个eclipse进程,我们运行在其中的程序就可以理解为线程。 二、为什么要使用线程 (1)更多的处理器核心(可以运行更多的线程)。 (2)更快的响应时间(线程可以并行执行)。 (3)更好的编程模型。 三、线程的状态 Java线程在运行的生命周期中有6中不同的状态,在给定的一个时刻,线程只能处于其中一个状态。如下图所示。
状态名称 | 说明 |
---|---|
NEW | 初始状态,线程被创建,但是还没有调用start方法。 |
RUNNABLE | 运行状态,Java线程将操作系统中的就绪和运行两种状态统称地称作“运行中” |
BLOCKED | 阻塞状态,表示线程阻塞于锁 |
WAITING | 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或中断) |
TIME_WAITING | 超时状态,该状态不同于WAITING,它是可以在指定时间自行返回的 |
TERMINATED | 停止状态,表示当前线程已经执行完毕 |
四、线程的调度(状态的变化) 我们先来看一张图:线程的调度对线程状态的影响 1)NEW(状态)线程创建未启动时的状态。如下代码示例:
Thread thread = new Thread(new ThreadTest());
System.out.println(thread.getState());
输出结果: NEW
Process finished with exit code 0 2)NEW-RUNNABLE线程调用start方法。如下代码示例:
Thread thread = new Thread(new ThreadTest());
thread.start();
System.out.println(thread.getState());
输出结果: RUNNABLE 线程调用yield()方法,yield方法的作用就是让出CPU,当前线程从运行中变为可运行状态(READY),让和它同级或者更高级别的线程运行,但是不能保证运行的线程立马变成可运行状态(不确定的)。看如下代码示例: 代码设置了线程的优先级,但是测试了几次的测试结果都不相同。
**
* 测试yield方法
*/
public class ThreadYieldTest {
public static void main (String[] args) {
Thread threadone = new Thread(new ThreadTestOne(),"ThreadTestOne");
Thread threadtwo = new Thread(new ThreadTestTwo(),"ThreadTestTwo");
threadone.setPriority(Thread.MIN_PRIORITY);
threadtwo.setPriority(Thread.MAX_PRIORITY);
threadone.start();
threadtwo.start();
}
static class ThreadTestOne implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("ThreadTestOne----MIN_PRIORITY-----"+i);
Thread.yield();
}
}
}
static class ThreadTestTwo implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("ThreadTestTwo-----MAX_PRIORITY----"+i);
Thread.yield();
}
}
}
}
结果一: ThreadTestOne----MIN_PRIORITY-----0 ThreadTestTwo-----MAX_PRIORITY----0 ThreadTestOne----MIN_PRIORITY-----1 ThreadTestTwo-----MAX_PRIORITY----1 结果二: ThreadTestTwo-----MAX_PRIORITY----0 ThreadTestOne----MIN_PRIORITY-----0 ThreadTestTwo-----MAX_PRIORITY----1 ThreadTestOne----MIN_PRIORITY-----1 ThreadTestTwo-----MAX_PRIORITY----2 ThreadTestTwo-----MAX_PRIORITY----3
3)RUNNABLE-WAITING(调用wait、join等方法) (1)、wait方法可以让一个线程的状态变为WAITING或者TIME_WAITING,wait方法的作用是让当前线程进入等待队列,让出CPU的执行权,线程的状态变化为等待状态,执行wait方法的前提就是获取到对象锁,因为执行线程需要知道进入谁的等待队列,之后才能被谁唤醒。看如下代码示例:(jps 看java的进程id,jstack 看java线程的信息)
static class Parent implements Runnable {
@Override
public void run() {
synchronized (lock){
System.out.println("执行lock.wait");
try {
lock.wait();
// 不会执行
System.out.println(Thread.currentThread().getName()+"----------"+Thread.currentThread().getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
通过命令看下线程信息如下图所示:可以看到Thread.State:WAITING
(2)、join方法也可以使线程以让一个线程的状态变为WAITING或者TIME_WAITING,join的方法的作用,字面意思加入、参加。我们可以这么理解join方法,一个线程加入一个正在运行的主线程中,并且使得正在运行的主线程等待加入的线程执行完毕才能继续执行。看如下代码示例: 1、我们在main方法启动两个线程,分别调用jion方法和不调用,看下执行结果一(不调用join方法): Parent----------------- Child----------- 结果顺序不确定 Child----------- Parent-----------------
public static void main (String[] args) {
Thread thread = new Thread(new Parent());
thread.setName("Parent");
Thread threadChild = new Thread(new Child());
threadChild.setName("Child");
thread.start();
threadChild.start();
}
/**
* 父线程
*/
static class Parent implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-----------------");
}
}
/**
* 子线程
*/
static class Child implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-----------");
}
}
结果二(其中一个调用join方法) (多次运行结果顺序不变) Parent----------------- Child-----------
public static void main (String[] args) {
Thread thread = new Thread(new Parent());
thread.setName("Parent");
Thread threadChild = new Thread(new Child());
threadChild.setName("Child");
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
threadChild.start();
}
2、join方法源码 可以看出join方法是调用的wait方法,所以线程的状态会变成WAITING或者TIME_WAITING。 我们来分析下是哪个线程加入等待队列,可以看出join方法是个synchronized方法,也就是说锁对象就是调用者,然后拥有锁对象的线程调用wait方法进入等待队列。我们通过上面的main方法来分析到底是谁进入等待队列,主角有main线程、Parent线程、Child线程,我们在main线程里面new了Parent线程和Child线程,然后在Child线程启动前面调用了Parent线程的join方法,也就是说是Parent线程调用了join方法,所以锁对象就是Parent线程实例,我们再来分析是那个线程拥有这个锁对象,答案是main线程,所以main线程调用wait方法进入等待队列Parent线程执行完毕;join方法结束时会唤醒主线程。
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
4)RUNNABLE-TIME_WAITING(调用sleep、wait、join等方法) sleep(long)方法就是让线程睡眠一定的时间在执行,不过这个是有时间限制的,到了时间就会又变成RUNNABLE状态。如下代码示例:
static class Parent implements Runnable {
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println("-----------------------");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果 join(long)和wait(long)方法和WAITING状态的方式类似,只是加入时间后线程可以自动唤醒,自动从等待队列加入同步队列,获取到锁变成RUNNABLE状态。 sleep和wait的区别: 相同点:sleep和wait都可以使线程等待特定的时间;都可以使用interrupt()后调用阻塞方法中断线程; 不同点:sleep是Thread方法,wait是Object的方法;slepp到了时间自动唤醒,而wait没有规定时间时需要手动唤醒;在synchronized关键字修饰的方法或者块中,sleep不会释放锁,wait会释放锁。 4)TIME_WAITING or WAITING-RUNNABLE(调用notify()、notifyAll(),sleep时间到了) sleep时间到了线程就会进入RUNNABLE状态(但是可能是RUNNING or READY状态)。 notify()是唤醒一个线程,进入同步队列,没有获取到锁就是BLOCKED状态,获取到锁就是RUNNABLE状态。 notifyAll()是唤醒等待队列的所有线程进入同步队列。 5)RUNNABLE-BLOCKED(线程获取锁失败,进入同步队列) 如下代码示例:
static class Parent implements Runnable {
@Override
public void run() {
synchronized (lock) {
System.out.println(Thread.currentThread().getName()+"----------------------------------");
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 子线程
*/
static class Child implements Runnable {
@Override
public void run() {
synchronized (lock) {
System.out.println(Thread.currentThread().getName()+"----------------------------------");
}
}
}
结果 6)RUNNABLE-TERMINATED(线程执行完毕) 五、如何优雅终止线程(手动) 我们知道线程提供了interrupt()、stop()方法;中断线程的三种方式; 1)stop方法,停止一个线程,现在已经是一个过期方法(不推荐使用)。 代码示例:
public static void main (String[] args) throws InterruptedException {
Thread thread = new Thread(new Parent(),"Parent");
thread.start();
Thread.sleep(200);
thread.stop();
}
/**
* 父线程
*/
static class Parent implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("-----------------------");
}
}
}
- 使用interrupt()方法,只是给线程设置了一个中断标记,并不会中断线程,配合阻塞方法才能实现线程的中断。 如下代码示例: 中断了 java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at com.ge.thread.method.ThreadStopTest$Parent.run(ThreadStopTest.java:30) at java.lang.Thread.run(Thread.java:745)
public static void main (String[] args) {
Thread thread = new Thread(new Parent(),"Parent");
thread.start();
thread.interrupt();
}
/**
* 父线程
*/
static class Parent implements Runnable {
@Override
public void run() {
try {
while (true) {
/*System.out.println("------------");
// 测试 isInterrupted方法
System.out.println("0isInterrupted------"+Thread.currentThread().isInterrupted());
// 测试interrupted方法
System.out.println("1interrupted------"+ Thread.interrupted());
System.out.println("2interrupted------"+ Thread.interrupted());*/
Thread.sleep(500);
}
}catch (InterruptedException e) {
e.printStackTrace();
System.out.println("中断了");
}
}
}
interrupt、interrupted和isInterrupted方法的区别,大家可以运行上面注释代码看运行结果。 interrupt:给线程一个中断标记,配合阻塞方法中断线程,抛出InterruptedException异常,并清除标记。 interrupted:Thread的静态方法,返回线程的中断标记状态,并且清理,所以第一次返回true或者false,第二次一定是false。 isInterrupted:返回线程的中断标记状态。 3)给线程的执行设置一个标记,满足就执行,不满足就结束线程。 如下代码示例:
private static volatile boolean flag = true;
public static void main (String[] args) {
Thread thread = new Thread(new Parent());
thread.setName("Parent");
thread.start();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 中断线程标记
flag = false;
}
/**
* 父线程
*/
static class Parent implements Runnable {
@Override
public void run() {
while (flag && !Thread.currentThread().isInterrupted()) {
System.out.println("--------------");
}
}
}
4)总结,stop方法就是强行结束线程,不推荐可能造成业务数据未知的错误,因为线程运行在那个过程时未知的;interrupt通过标记和阻塞方法一起中断线程,会抛出异常,但是要对异常做处理,例如处理线程为执行完成的任务或者回滚保证业务正确;推荐标记法,因为线程会走一个完整的过程,不会出现业务方面的未知错误,线程要么执行,要么不执行,不会执行一半就退出,所以就不会出现未知错误。 六、总结 本文针对线程的生命周期,线程的每个状态进行解释,以及线程执行过程的状态变化;线程调度对线程状态的影响,以及一些线程的基本方法;最后介绍了停止线程的三种方式;通过学习这些有助于我们理解线程的基础,为学习多线程打下基础,只有学习好了单线程,才能更好的学习多线程;希望与诸君共勉。