写在前面的话:此文只能说是java多线程的一个入门,其实Java里头线程完全可以写一本书了,但是如果最基本的你都学掌握好,又怎么能更上一个台阶呢?如果你觉得此文很简单,那推荐你看看Java并发包的的线程池(Java并发编程与技术内幕:线程池深入理解),或者看这个专栏:Java并发编程与技术内幕。你将会对Java里头的高并发场景下的线程有更加深刻的理解。

本文主要讲java实现多线程的四种方法中的两种(继承Thread类,实现Runnable接口)。
创建执行线程有四种方式:

1.继承Thread类,重写run方法
2.实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target
3.通过Callable和FutureTask创建线程
4.通过线程池创建线程

前面两种可以归结为一类:无返回值,原因很简单,通过重写run方法,run方式的返回值是void,所以没有办法返回结果;
后面两种可以归结成一类:有返回值,通过Callable接口,就要实现call方法,这个方法的返回值是Object,所以返回的结果可以放在Object对象中;
本文不对后两种创建多线程的方式做介绍,有兴趣看这里Java并发编程与技术内幕:Callable、Future、FutureTask、CompletionService )

在这之前,首先让我们来了解下在操作系统中进程和线程的区别:

  进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1–n个线程。(进程是资源分配的最小单位)

  线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)

  线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。

  多进程是指操作系统能同时运行多个任务(程序)。

  多线程是指在同一程序中有多个顺序流在执行。

一、扩展java.lang.Thread类

这里继承Thread类的方法是比较常用的一种,如果说你只是想起一条线程。没有什么其它特殊的要求,那么可以使用Thread.(笔者推荐使用Runable,后头会说明为什么)。下面来看一个简单的实例

package day001;
/**
*
* 项目名称:JavaThread
* 类名称:TestThread
* 类描述:测试java通过继承Thread类实现多线程
* 创建人:liuc
* 创建时间:2018年3月15日 下午4:20:28
* 修改人:liuc
* 修改时间:2018年3月15日 下午4:20:28
* 修改备注:
* @version
*/
public class TestThread extends Thread{
    private String str;

    /**
     * @Title:  TestThread   
     * @Description: 
     * @param:  @param string  
     * @throws   
    */

    public TestThread(String str) {
        this.str = str;
    }

    /**
    * (non-Javadoc)
    * @see java.lang.Thread#run()
    */
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(str+"线程:"+i);
        }
    }

    public static void main(String[] args) {
        TestThread t1 = new TestThread("A");
        TestThread t2 = new TestThread("B");
        t1.start();
        t2.start();
    }
}

输出:

B线程:0
A线程:0
B线程:1
B线程:2
A线程:1
B线程:3
A线程:2
B线程:4
A线程:3
A线程:4
A线程:5
A线程:6
A线程:7
B线程:5
B线程:6
B线程:7
B线程:8
B线程:9
A线程:8
B线程:10
B线程:11
B线程:12
A线程:9
B线程:13
A线程:10
B线程:14
A线程:11
B线程:15
B线程:16
A线程:12
B线程:17
A线程:13
B线程:18
A线程:14
A线程:15
A线程:16
A线程:17
A线程:18
B线程:19
A线程:19

注意:start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。
从程序运行的结果可以发现,多线程程序是乱序执行。因此,只有乱序执行的代码才有必要设计为多线程。
Thread.sleep()方法调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。
实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。

但是start方法重复调用的话,会出现java.lang.IllegalThreadStateException异常。

package day001;

/**
*
* 项目名称:JavaThread
* 类名称:TestThread
* 类描述:测试java通过继承Thread类实现多线程
* 创建人:liuc
* 创建时间:2018年3月15日 下午4:20:28
* 修改人:liuc
* 修改时间:2018年3月15日 下午4:20:28
* 修改备注:
* @version
*
*/
public class TestThread extends Thread{

    private String str;

    /**
     * @Title:  TestThread   
     * @Description:    TODO(这里用一句话描述这个方法的作用)   
     * @param:  @param string  
     * @throws   
    */
    public TestThread(String str) {
        this.str = str;
    }

    /**
    * (non-Javadoc)
    * @see java.lang.Thread#run()
    */
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(str+"线程:"+i);
        }

    }

    public static void main(String[] args) {
        TestThread t1 = new TestThread("A");
        TestThread t2 = t1;
        t1.start();
        t2.start();
    }
}

输出:

Exception in thread "main" java.lang.IllegalThreadStateException
A线程:0
A线程:1
A线程:2
A线程:3
A线程:4
A线程:5
A线程:6
A线程:7
A线程:8   at java.lang.Thread.start(Thread.java:708)
A线程:9

    at day001.TestThread.main(TestThread.java:61)
A线程:10
A线程:11
A线程:12
A线程:13
A线程:14
A线程:15
A线程:16
A线程:17
A线程:18
A线程:19

二、实现java.lang.Runnable接口

采用Runnable也是非常常见的一种,我们只需要重写run方法即可。下面也来看个实例。

/**
* 文件名:TestRunabled.java
*
* 版本信息:
* 日期:2018年3月15日
* Copyright 足下 Corporation 2018
* 版权所有
*
*/

package day001;

/**
*
* 项目名称:JavaThread
* 类名称:TestRunabled
* 类描述:测试java通过实现Runnable类来实现多线程
* 创建人:liuc
* 创建时间:2018年3月15日 下午5:29:48
* 修改人:liuc
* 修改时间:2018年3月15日 下午5:29:48
* 修改备注:
* @version
*
*/
public class TestRunnable implements Runnable{

    private String str;
    /**
     * @Title:  TestRunnable   
     * @Description:    TODO(这里用一句话描述这个方法的作用)   
     * @param:  @param string  
     * @throws   
    */

    public TestRunnable(String str) {
        this.str = str;
    }
    /**
    * (non-Javadoc)
    * @see java.lang.Runnable#run()
    */

    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(str+"线程:"+i);
        }
    }
    public static void main(String[] args) {
        Runnable oneRunnable = new TestRunnable("A");
        Runnable towRunnable = new TestRunnable("B");
        Thread oneThread = new Thread(oneRunnable);   
        Thread towThread = new Thread(towRunnable); 
        oneThread.start(); 
        towThread.start();
    }
}

输出:

A线程:0
B线程:0
A线程:1
B线程:1
A线程:2
B线程:2
A线程:3
B线程:3
B线程:4
B线程:5
B线程:6
B线程:7
A线程:4
A线程:5
A线程:6
A线程:7
A线程:8
A线程:9
A线程:10
A线程:11
A线程:12
A线程:13
A线程:14
A线程:15
A线程:16
A线程:17
A线程:18
A线程:19
B线程:8
B线程:9
B线程:10
B线程:11
B线程:12
B线程:13
B线程:14
B线程:15
B线程:16
B线程:17
B线程:18
B线程:19

说明:
TestRunnable类通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个约定。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。
在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。
实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。

三、Thread和Runnable的区别

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

总结:

实现Runnable接口比继承Thread类所具有的优势:

1):适合多个相同的程序代码的线程去处理同一个资源

2):可以避免java中的单继承的限制

3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类

提醒一下大家:main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。

在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个jvm,每一个jvm实习在就是在操作系统中启动了一个进程。