1、进程与线程的概念 

现在几乎所有操作系统都支持多任务,通常一个任务就是一个程序,一个运行中的程序就是一个进程。当一个程序行时,其内部也可能在执行多个任务,进程内每一个任务的执行流,就是一个线程。

所以线程也被称作轻量级进程。

总而言之,线程是进程的组成部分,可以独立、并发的执行任务

2、线程的创建和启动

Java中有两种方式来创建和启动线程。

2.1继承Thread类创建和启动线程

通过继承Thread类创建并启动多线程的步骤如下:

1、创建Thread的子类,并重写run方法。run方法中就是线程要执行的任务,所以也把run方法称为线程执行体。

2、创建该子类的实例,即线程对象。

3、使用线程对象的start方法启动线程。

线程类代码如下:

1 //通过继承Thread类来创建线程类
 2 
 3 public class ThreadOne extends Thread{
 4 
 5     private int i;
 6 
 7    
 8 
 9     //重写run方法,方法体就是线程要执行的任务
10 
11     @Override
12 
13     public void run() {
14 
15        for (i = 0; i < 20; i++) {
16 
17            //继承Thread类时,可以直接调用getName()方法来返回当前线程的名字
18 
19            //如果想获取当前线程,直接使用this即可
20 
21            System.out.println(getName()+" "+i);
22 
23        }
24 
25     }
26 
27 }

 

测试代码如下:

1 public class TestThreadOne {
 2 
 3     public static void main(String[] args) {
 4 
 5        for (int i = 0; i < 10; i++) {
 6 
 7            //打印主线线程的信息
 8 
 9            System.out.println(Thread.currentThread().getName()+" "+i);
10 
11            if (i==3) {
12 
13               //创建并启动第一条线程
14 
15               new ThreadOne().start();
16 
17               //创建并启动第二条线程
18 
19               new ThreadOne().start();
20 
21            }
22 
23        }
24 
25     }
26 
27 }

 

部分结果如下:

main 0
main 1
main 2
main 3
Thread-0 0
Thread-1 0
main 4
Thread-1 1
Thread-0 1
Thread-1 2
main 5

 

从上面的运行结果可以发现,测试类只显式的创建了两条线程,但实际上有三条线程在运行,即主线程、线程0和线程1。其中主线程的线程体是main方法里的内容。

此外,Thread-0和Thread-1两条线程输出的变量i的值是不连续的,而i并非是局部变量,而是实例成员变量,从这一点可以看出,每次创建线程都创建了一个ThreadOne对象。

2.2实现Runnable接口创建线程

实现Runnable接口创建并启动线程的步骤如下:

1、创建Runnable接口的实现类,重写run方法。run方法的内容即是线程要执行的任务。

2、创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象。该Thread对象才是真正的线程类。

 

线程类代码如下:

1 //通过实现Runnable接口创建线程类
 2 
 3 public class ThreadTwo implements Runnable{
 4 
 5     private int i;
 6 
 7    
 8 
 9     //run方法同样是线程的执行体
10 
11     @Override
12 
13     public void run() {
14 
15        for (i = 0; i < 5; i++) {
16 
17            //实现Runnable接口创建线程类时,只能使用Thread.currentThread()来获取当前线程
18 
19            System.out.println(Thread.currentThread().getName()+" "+i);
20 
21        }
22 
23     }
24 
25 }

 

测试代码如下:

1 public class TestThreadTwo {
 2 
 3     public static void main(String[] args) {
 4 
 5        for (int i = 0; i < 5; i++) {
 6 
 7            //打印主线线程的信息
 8 
 9            System.out.println(Thread.currentThread().getName()+" "+i);
10 
11            if (i==3) {
12 
13               ThreadTwo threadTwo = new ThreadTwo();
14 
15               //创建并启动第一条线程
16 
17               new Thread(threadTwo).start();
18 
19               //创建并启动第二条线程
20 
21               new Thread(threadTwo).start();
22 
23            }
24 
25        }
26 
27     }
28 
29 }

 

运行结果如下:

1 main 0
 2 
 3 main 1
 4 
 5 main 2
 6 
 7 main 3
 8 
 9 Thread-0 0
10 
11 Thread-1 0
12 
13 main 4
14 
15 Thread-1 2
16 
17 Thread-0 1
18 
19 Thread-0 4
20 
21 Thread-1 3

 

从运行结果可以看出,Thread-0和Thread-1打印的i值是连续的,说明这两条线程是共享一个实例的。这是因为,我们所创建的两个Thread类使用同一个Runnable对象作为target。

在实际开发中,推荐使用实现Runnable接口的方式来创建线程,这样还可以实现或继

2.3有返回值的线程

从JDK1.5开始,java提供Callable接口和Future接口以获得线程的返回值。

Callable类似Runnable的加强版,提供一个call()方法作为线程的执行体。与Runnable的run()方法相比,call()方法更强大:

  • call()方法可以有返回值
  • call()可以声明抛出异常

不过Callable对象并不能直接作为Thread的target,必须使用Future对象包装一下。具体使用步骤如下:

  • 创建Callable的实现类,并实现call方法,注意有泛型限制。
  • 使用FutureTask包装Callable对象。
  • 把FutureTask作为target传入Thread,创建启动线程。
  • 调用FutureTask对象的方法获取返回值。

线程代码:

1 public class CallableOne implements Callable<Integer>{
 2 
 3     @Override
 4 
 5     public Integer call() throws Exception {
 6 
 7        int i = 0;
 8 
 9        for(i=0;i<10;i++){
10 
11            System.out.println(Thread.currentThread().getName()+" "+i);
12 
13        }
14 
15        //线程返回值,模拟返回前阻塞主线程
16 
17        Thread.sleep(5000);
18 
19        return i;
20 
21     }
22 
23 }

 

测试代码:

1 public class TestCallable {
 2 
 3     public static void main(String[] args) {
 4 
 5        //创建Callable对象
 6 
 7        CallableOne callableOne = new CallableOne();
 8 
 9        //使用FutureTask包装Callable对象
10 
11        FutureTask<Integer> futureTask = new FutureTask<Integer>(callableOne);
12 
13        for(int i=0;i<100;i++){
14 
15            System.out.println(Thread.currentThread().getName()+" "+i);
16 
17            //i等于3时启动子线程,此时可见主线程与子线程交替执行
18 
19            if (i==3) {
20 
21               //FutureTask是Runnable的子类,可传入Thread当作target
22 
23               new Thread(futureTask).start();
24 
25            }
26 
27            //i等于80的时候来获取子线程的返回值,此时主线程会阻塞,直到返回了结果
28 
29            if (i==80) {
30 
31               try {
32 
33                   System.out.println("线程的返回值:"+futureTask.get());
34 
35               } catch (Exception e) {
36 
37                   e.printStackTrace();
38 
39               }
40 
41            }
42 
43        }
44 
45     }
46 
47 }

 

以上代码中,在i等于3时启动子线程,此时可见主线程与子线程交替执行。在i等于80的时候来获取子线程的返回值,此时主线程会阻塞,直到返回结果。