一、多线程的实现方式
Java多线程实现方式主要有三种:继承Thread类、实现Runnable接口、实现Callable接口通过FutureTask包装器来创建Thread线程。
其中前两种方式线程执行完后都没有返回值,后一种是带返回值的。
1、第一种实现方式:继承Thread类
- 继承Java.lang.Thread类,重写run()方法,将多线程代码块写入到run()方法中。
- 启动线程方式:实例化本类对象,然后调用start()方法。
/** * 继承Thread类,重写run()方法 */ class MyThread extends Thread{ @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(this.getName()+" , i = "+i); //getName(),获取当前线程的名称 } } } public class Test { public static void main(String[] args) throws Exception { MyThread myThread = new MyThread(); MyThread myThread1 = new MyThread(); myThread.start(); //Thread-0 myThread1.start(); //Thread-1 } }
//程序运行结果: Thread-0 , i = 0 Thread-1 , i = 0 Thread-1 , i = 1 Thread-0 , i = 1 Thread-0 , i = 2 Thread-0 , i = 3 Thread-0 , i = 4 Thread-1 , i = 2 Thread-1 , i = 3 Thread-1 , i = 4
2、第二种实现方式:实现Runnable接口
- 实现Java.lang.Runnable接口,并重写run()方法,将多线程代码块写入到run()方法中。
- 启动线程方式:将本类对象作为参数传入给Thread的构造方法,来实例化Thread对象,然后调用Thread对象的start()方法。
/** * 实现Runnable接口,重写run()方法 */ class MyThread implements Runnable{ @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(this+" , i = "+i); } } } public class Test { public static void main(String[] args) throws Exception { Runnable myThread = new MyThread(); Runnable myThread1 = new MyThread(); new Thread(myThread).start(); new Thread(myThread1).start(); } }
//注:Thread类的构造方法,可以接收Runnable实例对象
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
3、第三种实现方式:实现Callable接口
- 实现java.util.concurrent.Callable接口,并重写call()方法,将多线程代码块写入到call()方法中。
- 启动线程方式:(1)创建Callable接口实例;(2)然后,创建FutureTask类的实例,并将Callable接口的实例作为参数传入FutureTask实例中;(3)最后,将FutureTask实例作为参数传入给Thread的构造方法,来实例化Thread对象,并调用Thread对象的start()方法,启动多线程。(4)另外,线程结束后,可以通过FutureTask对象的get()方法,获取方法call()方法的返回值。
- 注:FutureTask<V>是一个包装器,它通过接受Callable<V>来创建,它同时实现了Future和Runnable接口。
/** * 实现Callable接口,重写call()方法 */ class MyThread implements Callable { @Override public Object call() throws Exception { for (int i = 0; i < 5; i++) { System.out.println(this+" , i = "+i); } return this + " 线程结束"; } } public class Test { public static void main(String[] args) throws Exception { //创建Callable实例 Callable<String> myThread = new MyThread(); Callable<String> myThread1 = new MyThread(); //由Callable实例,来创建一个FutureTask<String>实例 //注释:FutureTask<V>是一个包装器,它通过接受Callable<V>来创建,它同时实现了Future和Runnable接口。 FutureTask<String> myTask = new FutureTask<>(myThread); FutureTask<String> myTask1 = new FutureTask<>(myThread1); //创建一个Thread对象,并调用start()方法来启动线程 new Thread(myTask).start(); new Thread(myTask1).start(); //如有必要,等待计算完成,然后检索其结果 System.out.println(myTask.get()); System.out.println(myTask1.get()); } }
//程序运行结果: com.study.grammar.MyThread@56c2b49a , i = 0 com.study.grammar.MyThread@762e2323 , i = 0 com.study.grammar.MyThread@56c2b49a , i = 1 com.study.grammar.MyThread@56c2b49a , i = 2 com.study.grammar.MyThread@762e2323 , i = 1 com.study.grammar.MyThread@56c2b49a , i = 3 com.study.grammar.MyThread@762e2323 , i = 2 com.study.grammar.MyThread@56c2b49a , i = 4 com.study.grammar.MyThread@762e2323 , i = 3 com.study.grammar.MyThread@56c2b49a 线程结束 com.study.grammar.MyThread@762e2323 , i = 4 com.study.grammar.MyThread@762e2323 线程结束
二、Thread类启动线程的底层实现
Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法会调用本类的start0()方法,start0()方法是一个native方法,它将启动一个新线程,并执行run()方法。
/** * Thread类的start0()方法 * Java开发里面有一门技术成为JNI技术(Java Native Interface),这门技术的特点是:使用Java来调用本机操作系统提供的函数。但是这样以来就存在一个缺点,即:不能离开特定的操作系统。 * 如果要想线程能够执行,严格来讲是由操作系统来启动线程,并分配系统资源的。 * 即:使用Thread类的start0()方法,不仅仅包括启动线程的执行代码,而且还包括调用操作系统函数进行资源的分配。 */ private native void start0();
/** * Thread类的start()方法 */ public synchronized void start() { //此异常属于RuntimeException的子类,属于选择性异常。 if (threadStatus != 0) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */ group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } }
三、Callable接口、Runnable接口,两者的区别
- 实现多线程时,Runnable接口的方法是run();Callable接口的方法是call()。
- 实现Runnable接口,任务没有返回值;实现Callable接口,任务可以设置返回值。
- Runnable无法抛出经过检查的异常,而Callable可以抛出经过检查的异常。
@FunctionalInterface public interface Callable<V> { V call() throws Exception; } @FunctionalInterface public interface Runnable { public abstract void run(); } Callable接口,返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做 call 的方法。 Callable接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable不会返回结果,并且无法抛出经过检查的异常。 Executors 类包含一些从其他普通形式转换成 Callable 类的实用方法。
四、使用ExecutorService、Callable、Future实现有返回结果的线程
ExecutorService、Callable、Future三个接口实际上都是属于Executor框架。返回结果的线程是在JDK1.5中引入的新特征,有了这种特征就不需要再为了得到返回值而大费周折了。而且自己实现了也可能漏洞百出。
执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了。再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了。
注意:get方法是阻塞的,即:线程无返回结果,get方法会一直等待。
下面提供了一个完整的有返回结果的多线程测试例子,在JDK1.5下验证过没问题可以直接使用。代码如下:
import java.util.concurrent.*; import java.util.Date; import java.util.List; import java.util.ArrayList; /** * 通过实现Callable接口,实现有返回值的线程 */ class MyCallable implements Callable<Object> { private String taskNum; MyCallable(String taskNum) { this.taskNum = taskNum; } /** * 重写多线程方法, */ public Object call() throws Exception { System.out.println(">>>" + taskNum + "任务启动"); Date dateTmp1 = new Date(); Thread.sleep(1000); Date dateTmp2 = new Date(); long time = dateTmp2.getTime() - dateTmp1.getTime(); System.out.println(">>>" + taskNum + "任务终止"); return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】"; } } public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { System.out.println("----程序开始运行----"); int taskSize = 5; // 创建一个线程池 ExecutorService pool = Executors.newFixedThreadPool(taskSize); // 创建多个有返回值的任务 List<Future> list = new ArrayList<Future>(); for (int i = 0; i < taskSize; i++) { Callable c = new MyCallable(i + " "); // 执行任务并获取Future对象 Future f = pool.submit(c); //ExecutorService.submit(),提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。 list.add(f); } // 关闭线程池 pool.shutdown(); // 获取所有并发任务的运行结果 for (Future f : list) { // 从Future对象上获取任务的返回值,并输出到控制台 System.out.println(">>>" + f.get().toString()); } } }
//程序运行结果 ----程序开始运行---- >>>0 任务启动 >>>1 任务启动 >>>2 任务启动 >>>3 任务启动 >>>4 任务启动 >>>4 任务终止 >>>1 任务终止 >>>0 任务终止 >>>3 任务终止 >>>0 任务返回运行结果,当前任务时间【1005毫秒】 >>>1 任务返回运行结果,当前任务时间【1005毫秒】 >>>2 任务终止 >>>2 任务返回运行结果,当前任务时间【1005毫秒】 >>>3 任务返回运行结果,当前任务时间【1005毫秒】 >>>4 任务返回运行结果,当前任务时间【1005毫秒】
代码说明: 上述代码中Executors类,提供了一系列工厂方法用于创建线程池,返回的线程池都实现了ExecutorService接口。 public static ExecutorService newFixedThreadPool(int nThreads) 创建固定数目线程的线程池。 public static ExecutorService newCachedThreadPool() 创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。 public static ExecutorService newSingleThreadExecutor() 创建一个单线程化的Executor。 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。 ExecutoreService提供了submit()方法,传递一个Callable,或Runnable,返回Future。如果Executor后台线程池还没有完成Callable的计算,这调用返回Future对象的get()方法,会阻塞直到计算完成。