前言
多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们来说极其重要,下面跟我一起开启本次的学习之旅吧。
正文
线程与进程
1 线程:进程中负责程序执行的执行单元
线程本身依靠程序进行运行
线程是程序中的顺序控制流,只能使用分配给程序的资源和环境
2 进程:执行中的程序
一个进程至少包含一个线程
3 单线程:程序中只存在一个线程,实际上主方法就是一个主线程
4 多线程:在一个程序中运行多个任务
目的是更好地使用CPU资源
线程的实现
继承Thread类
在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方法调用的相关信息,说明新线程创建的过程不会阻塞主线程的后续执行。
实现Runnable接口
在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实现有返回结果的多线程
多线程后续会学到,这里暂时先知道一下有这种方法即可。
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对象上获取任务的返回值,并输出到控制台