一、什么是线程
要理解什么线程,我么得先知道什么是进程。现代操作系统在运行一个程序时,会为其创建一个进程。例如启动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("-----------------------");
}
}
}
2) 使用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通过标记和阻塞方法一起中断线程,会抛出异常,但是要对异常做处理,例如处理线程为执行完成的任务或者回滚保证业务正确;推荐标记法,因为线程会走一个完整的过程,不会出现业务方面的未知错误,线程要么执行,要么不执行,不会执行一半就退出,所以就不会出现未知错误。
六、总结
本文针对线程的生命周期,线程的每个状态进行解释,以及线程执行过程的状态变化;线程调度对线程状态的影响,以及一些线程的基本方法;最后介绍了停止线程的三种方式;通过学习这些有助于我们理解线程的基础,为学习多线程打下基础,只有学习好了单线程,才能更好的学习多线程;希望与诸君共勉。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。