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