前言

多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们来说极其重要,下面跟我一起开启本次的学习之旅吧。

正文

线程与进程

1 线程:进程中负责程序执行的执行单元
线程本身依靠程序进行运行
线程是程序中的顺序控制流,只能使用分配给程序的资源和环境

2 进程:执行中的程序
一个进程至少包含一个线程

3 单线程:程序中只存在一个线程,实际上主方法就是一个主线程

4 多线程:在一个程序中运行多个任务
目的是更好地使用CPU资源

线程的实现

继承Thread类

java.lang包中定义, 继承Thread类必须重写run()方法

【Java学习笔记之三十四】超详解Java多线程基础_ide
 1 class MyThread extends Thread{
 2     private static int num = 0;
 3  
 4     public MyThread(){
 5         num++;
 6     }
 7  
 8     @Override
 9     public void run() {
10         System.out.println("主动创建的第"+num+"个线程");
11     }
12 }
【Java学习笔记之三十四】超详解Java多线程基础_ide

创建好了自己的线程类之后,就可以创建线程对象了,然后通过start()方法去启动线程。注意,不是调用run()方法启动线程,run方法中只是定义需要执行的任务,如果调用run方法,即相当于在主线程中执行run方法,跟普通的方法调用没有任何区别,此时并不会创建一个新的线程来执行定义的任务。

 

【Java学习笔记之三十四】超详解Java多线程基础_ide
 1 public class Test {
 2     public static void main(String[] args)  {
 3         MyThread thread = new MyThread();
 4         thread.start();
 5     }
 6 }
 7 class MyThread extends Thread{
 8     private static int num = 0;
 9     public MyThread(){
10         num++;
11     }
12     @Override
13     public void run() {
14         System.out.println("主动创建的第"+num+"个线程");
15     }
16 }
【Java学习笔记之三十四】超详解Java多线程基础_ide

 

在上面代码中,通过调用start()方法,就会创建一个新的线程了。为了分清start()方法调用和run()方法调用的区别,请看下面一个例子:

 

【Java学习笔记之三十四】超详解Java多线程基础_ide
 1 public class Test {
 2     public static void main(String[] args)  {
 3         System.out.println("主线程ID:"+Thread.currentThread().getId());
 4         MyThread thread1 = new MyThread("thread1");
 5         thread1.start();
 6         MyThread thread2 = new MyThread("thread2");
 7         thread2.run();
 8     }
 9 }
10  
11 class MyThread extends Thread{
12     private String name;
13  
14     public MyThread(String name){
15         this.name = name;
16     }
17  
18     @Override
19     public void run() {
20         System.out.println("name:"+name+" 子线程ID:"+Thread.currentThread().getId());
21     }
22 }
【Java学习笔记之三十四】超详解Java多线程基础_ide

 

运行结果:

【Java学习笔记之三十四】超详解Java多线程基础_多线程_07

从输出结果可以得出以下结论:

1)thread1和thread2的线程ID不同,thread2和主线程ID相同,说明通过run方法调用并不会创建新的线程,而是在主线程中直接运行run方法,跟普通的方法调用没有任何区别;

2)虽然thread1的start方法调用在thread2的run方法前面调用,但是先输出的是thread2的run方法调用的相关信息,说明新线程创建的过程不会阻塞主线程的后续执行。

实现Runnable接口

在Java中创建线程除了继承Thread类之外,还可以通过实现Runnable接口来实现类似的功能。实现Runnable接口必须重写其run方法。
下面是一个例子:

【Java学习笔记之三十四】超详解Java多线程基础_ide
 1 public class Test {
 2     public static void main(String[] args)  {
 3         System.out.println("主线程ID:"+Thread.currentThread().getId());
 4         MyRunnable runnable = new MyRunnable();
 5         Thread thread = new Thread(runnable);
 6         thread.start();
 7     }
 8 } 
 9 class MyRunnable implements Runnable{
10     public MyRunnable() {
11     }
12  
13     @Override
14     public void run() {
15         System.out.println("子线程ID:"+Thread.currentThread().getId());
16     }
17 }
【Java学习笔记之三十四】超详解Java多线程基础_ide

Runnable的中文意思是“任务”,顾名思义,通过实现Runnable接口,我们定义了一个子任务,然后将子任务交由Thread去执行。注意,这种方式必须将Runnable作为Thread类的参数,然后通过Thread的start方法来创建一个新线程来执行该子任务。如果调用Runnable的run方法的话,是不会创建新线程的,这根普通的方法调用没有任何区别。

事实上,查看Thread类的实现源代码会发现Thread类是实现了Runnable接口的。

在Java中,这2种方式都可以用来创建线程去执行子任务,具体选择哪一种方式要看自己的需求。直接继承Thread类的话,可能比实现Runnable接口看起来更加简洁,但是由于Java只允许单继承,所以如果自定义类需要继承其他类,则只能选择实现Runnable接口。

使用ExecutorService、Callable、Future实现有返回结果的多线程

多线程后续会学到,这里暂时先知道一下有这种方法即可。

ExecutorService、Callable、Future这个对象实际上都是属于Executor框架中的功能类。返回结果的线程是在JDK1.5中引入的新特征,确实很实用,有了这种特征我就不需要再为了得到返回值而大费周折了,而且即便实现了也可能漏洞百出。

可返回值的任务必须实现Callable接口,类似的,无返回值的任务必须Runnable接口。执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了,再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了。下面提供了一个完整的有返回结果的多线程测试例子,在JDK1.5下验证过没问题可以直接使用。代码如下:

【Java学习笔记之三十四】超详解Java多线程基础_ide
 1 /**
 2 * 有返回值的线程 
 3 */ 
 4 @SuppressWarnings("unchecked")  
 5 public class Test {  
 6 public static void main(String[] args) throws ExecutionException,  
 7     InterruptedException {  
 8    System.out.println("----程序开始运行----");  
 9    Date date1 = new Date();  
10  
11    int taskSize = 5;  
12    // 创建一个线程池  
13    ExecutorService pool = Executors.newFixedThreadPool(taskSize);  
14    // 创建多个有返回值的任务  
15    List<Future> list = new ArrayList<Future>();  
16    for (int i = 0; i < taskSize; i++) {  
17     Callable c = new MyCallable(i + " ");  
18     // 执行任务并获取Future对象  
19     Future f = pool.submit(c);  
20     // System.out.println(">>>" + f.get().toString());  
21     list.add(f);  
22    }  
23    // 关闭线程池  
24    pool.shutdown();  
25  
26    // 获取所有并发任务的运行结果  
27    for (Future f : list) {  
28     // 从Future对象上获取任务的返回值,并输出到控制台