一、什么是线程
要理解什么线程,我么得先知道什么是进程。现代操作系统在运行一个程序时,会为其创建一个进程。例如启动eclipse.exe其实就是启动了win系统的一个进程。现代操作系统调度的最小单元就是线程,也叫轻量级进程,在一个进程里面包含多个线程,这些线程都有各自的计数器、堆栈等,并且能够共享内存变量。例如我们启动了一个eclipse进程,我们运行在其中的程序就可以理解为线程。
二、为什么要使用线程
(1)更多的处理器核心(可以运行更多的线程)。
(2)更快的响应时间(线程可以并行执行)。
(3)更好的编程模型。
三、线程的状态
Java线程在运行的生命周期中有6中不同的状态,在给定的一个时刻,线程只能处于其中一个状态。如下图所示。

状态名称 说明
NEW 初始状态,线程被创建,但是还没有调用start方法。
RUNNABLE 运行状态,Java线程将操作系统中的就绪和运行两种状态统称地称作“运行中”
BLOCKED 阻塞状态,表示线程阻塞于锁
WAITING 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或中断)
TIME_WAITING 超时状态,该状态不同于WAITING,它是可以在指定时间自行返回的
TERMINATED 停止状态,表示当前线程已经执行完毕

四、线程的调度(状态的变化)
我们先来看一张图:线程的调度对线程状态的影响
Java 线程浅析
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
Java 线程浅析

(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();
            }
        }
    }

结果
Java 线程浅析
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()+"----------------------------------");
            }
        }
    }

结果Java 线程浅析
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通过标记和阻塞方法一起中断线程,会抛出异常,但是要对异常做处理,例如处理线程为执行完成的任务或者回滚保证业务正确;推荐标记法,因为线程会走一个完整的过程,不会出现业务方面的未知错误,线程要么执行,要么不执行,不会执行一半就退出,所以就不会出现未知错误。
六、总结
本文针对线程的生命周期,线程的每个状态进行解释,以及线程执行过程的状态变化;线程调度对线程状态的影响,以及一些线程的基本方法;最后介绍了停止线程的三种方式;通过学习这些有助于我们理解线程的基础,为学习多线程打下基础,只有学习好了单线程,才能更好的学习多线程;希望与诸君共勉。