多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们来说极其重要,下面跟我一起开启本次的学习之旅吧。
1 线程:进程中负责程序执行的执行单元
线程本身依靠程序进行运行
线程是程序中的顺序控制流,只能使用分配给程序的资源和环境
2 进程:执行中的程序
一个进程至少包含一个线程
3 单线程:程序中只存在一个线程,实际上主方法就是一个主线程
4 多线程:在一个程序中运行多个任务
目的是更好地使用CPU资源
在java.lang
包中定义, 继承Thread类必须重写run()
方法
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 }
创建好了自己的线程类之后,就可以创建线程对象了,然后通过start()方法去启动线程。注意,不是调用run()方法启动线程,run方法中只是定义需要执行的任务,如果调用run方法,即相当于在主线程中执行run方法,跟普通的方法调用没有任何区别,此时并不会创建一个新的线程来执行定义的任务。
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 }
在上面代码中,通过调用start()方法,就会创建一个新的线程了。为了分清start()方法调用和run()方法调用的区别,请看下面一个例子:
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 }
运行结果:
从输出结果可以得出以下结论:
1)thread1和thread2的线程ID不同,thread2和主线程ID相同,说明通过run方法调用并不会创建新的线程,而是在主线程中直接运行run方法,跟普通的方法调用没有任何区别;
2)虽然thread1的start方法调用在thread2的run方法前面调用,但是先输出的是thread2的run方法调用的相关信息,说明新线程创建的过程不会阻塞主线程的后续执行。
在Java中创建线程除了继承Thread类之外,还可以通过实现Runnable接口来实现类似的功能。实现Runnable接口必须重写其run方法。
下面是一个例子:
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 }
Runnable的中文意思是“任务”,顾名思义,通过实现Runnable接口,我们定义了一个子任务,然后将子任务交由Thread去执行。注意,这种方式必须将Runnable作为Thread类的参数,然后通过Thread的start方法来创建一个新线程来执行该子任务。如果调用Runnable的run方法的话,是不会创建新线程的,这根普通的方法调用没有任何区别。
事实上,查看Thread类的实现源代码会发现Thread类是实现了Runnable接口的。
在Java中,这2种方式都可以用来创建线程去执行子任务,具体选择哪一种方式要看自己的需求。直接继承Thread类的话,可能比实现Runnable接口看起来更加简洁,但是由于Java只允许单继承,所以如果自定义类需要继承其他类,则只能选择实现Runnable接口。
多线程后续会学到,这里暂时先知道一下有这种方法即可。
ExecutorService、Callable、Future这个对象实际上都是属于Executor框架中的功能类。返回结果的线程是在JDK1.5中引入的新特征,确实很实用,有了这种特征我就不需要再为了得到返回值而大费周折了,而且即便实现了也可能漏洞百出。
可返回值的任务必须实现Callable接口,类似的,无返回值的任务必须Runnable接口。执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了,再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了。下面提供了一个完整的有返回结果的多线程测试例子,在JDK1.5下验证过没问题可以直接使用。代码如下:
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对象上获取任务的返回值,并输出到控制台
29 System.