多线程-2-线程创建与启动

来源:互联网 发布:淘宝美工工资怎么算 编辑:程序博客网 时间:2024/04/17 04:08

如果想要通过多线程做事,那么如何创建并启动线程就变得非常重要。本文将首先介绍全部3种创建线程的方式及1种启动线程方式(调用线程的.start()方法),之后再对3种方式进行对比。

Java中使用Tread类代表线程,所有线程对象都必须是Thread类或其子类(也就是Java多线程系列文章第1篇中多线程模型中的运转模型)的实例,OOP中真正做事的还是对象。每个线程都有线程任务,称之为线程执行体,其实就是特定代码块。


0.知识体系


线程创建与启动的知识体系如下图所示:



1.继承Thread类方式


1)创建步骤

此方式创建线程只需下列3步     
(1)定义Thread类子类,重写子类run方法,run方法体也就代表线程任务。

(2)创建Thread子类对象,即创建线程对象,此时线程处于其5种状态中的新建状态。

(3)如果启动线程对象,调用其start()方法,此时线程处于其5种状态中的就绪态(此处属于线程启动部分,参见线程启动方式)。


2)步骤演示


//1.1.定义Thread类子类。class FirstWayThread extends Thread{// 1.2.重写子类run方法,run方法体也就代表线程任务。public void run(){for(int i = 0; i < 23; i++){//Thread中getName()方法获取当前线程名字,此类为其子类,故也可直接调用getName()方法。System.out.println(getName() + "--" + i);}}}public class FirstWayThreadTest{public static void main(String[] args){//每个Java程序都至少有一个主线程,主线程执行体由main方法确定而非run方法。System.out.println(Thread.currentThread().getName() + "开始执行");//2.创建Thread子类对象,即创建线程对象,此时线程处于其5种状态中的新建状态。FirstWayThread fwt1 = new FirstWayThread();//fwt1.setName("Tad");//为线程设置名字FirstWayThread fwt2 = new FirstWayThread();//3.如果启动线程对象,调用其start()方法,此时线程处于其5种状态中的就绪态(此处属于线程启动部分,参见线程启动方式)。fwt1.start();fwt2.start();System.out.println(Thread.currentThread().getName() + "结束执行");}}

3)补充说明        


(1)currentThread()是Thread类静态方法,此方法返回当前正在执行的线程对象。

(2)getName()是Thread类实例方法,此方法返回调用该方法的线程名字。

(3)setName(String name)是Thread类实例方法,此方法可以为线程对象设置名字。默认情况下,主线程名字为main,用户创建线程名字依次为Thread-0、Thread-1、...、Thread-n。


2.实现Runnable接口方式


1)创建步骤


(1)定义Runnable接口实现类,重写run()方法,该方法就是线程任务,此时就得到之前文章说的业务模型。
(2)创建Runnable实现类对象,并以此对象作为Thread的target创建Thread对象,该对象就是线程对象,此时线程处于其5种状态中的新建状态。
(3)如果启动线程对象,调用其start()方法,此时线程处于其5种状态中的就绪态(此处属于线程启动部分,参见线程启动方式)。 
  注:Runnable对象仅是Thread对象的target,其内的run方法仅仅作为线程执行体,实际线程对象依然是Thread实例,Thread线程负责执行其target的run()方法,此处也就把业务模型与运转模型分开。

2)步骤演示


//1.1.定义Runnable接口实现类。class SecondWayThread implements Runnable{public void run() {// 1.2.重写run()方法,该方法就是线程任务。for(int i = 0; i < 23; i++){//非Thread子类,只能用全名System.out.println(Thread.currentThread().getName() + "--" + i);}}}public class SecondWayThreadTest {public static void main(String[] args) {//查看主线程情况System.out.println(Thread.currentThread().getName() + "开始执行");//2.1.创建Runnable实现类对象;SecondWayThread swt = new SecondWayThread(); //2.2.并以此对象作为Thread的target创建Thread对象,该对象就是线程对象,此时线程处于其5种状态中的新建状态。Thread swt1 = new Thread(swt,"New Thread");Thread swt2 = new Thread(swt);//3.如果启动线程对象,调用其start()方法,此时线程处于其5种状态中的就绪态(此处属于线程启动部分,参见线程启动方式)。swt1.start();swt2.start();System.out.println(Thread.currentThread().getName() + "结束执行");}}

此种方式创建线程最大的好处是多个线程可以共享同一个target。


3.使用Callable和Future接口方式


0)情况说明


实现Runnable接口创建线程,Thread类作用就是把run()方法包装成线程执行体,现在问题是run()方法形式是固定的,没有返回值并且不能抛出异常,如果想把任意方法包装成线程执行体,就需要采用Callable和Future接口。


(1) Callable接口

Callable接口提供了 call()方法作为线程执行体,call()方法两点优势明显胜于run()方法。

a)call()方法可以有返回值。
b)call()方法可以抛出异常。

以上可知识完成可以提供Callable对象作为Thread类的Target,线程执行体就是Callable对象的call()方法。现在的问题是Callable接口是Java5新增接口,非Runnable子接口(没有run方法,当然即使提供了run()方法也不行),所以Callable对象不能直接作为Thread的target。而且call()方法有返回值(并且能抛出异常),作为线程执行体被调用,如何获取call()方法的返回值呢?就用到了下面的Future接口。


(2)Future接口

Java 5提供了Future接口代表Callable接口里call()方法的返回值,并为Future接口提供了FutureTask实现类(下面会介绍)。Future定义了如下方法控制其关联的Callable任务。

a)  V get():返回Callable任务里call()方法的返回值。调用该方法将导致程序阻塞,必须等到子线程结束后才会得到返回值。
b.) V get(long timeout,TimeUnit unit):返回Callable任务里call()方法的返回值。该方法会让程序最多阻塞timeout和unit指定的时间,如果经过指定时间后Callable任务依然没有返回值,抛出TimeoutException异常。
c.) boolean cancel(boolean mayInterruptIfRunning):试图取消该Future关联的Callable任务。
d.) boolean isCancelled():如果在Callable任务正常完成前被取消,则返回true。

e) boolean isDone():如果Callable任务已完成,则返回true。

注:Callable接口有泛型限制,此接口里的泛型形参类型与call()方法返回值类型相同。


(3)FutureTask实现类

FutureTask实现类实现了Future接口和Runnable两个接口,因此可以作为Thread的target。


2)创建步骤

(1)创建Callable接口实现类,并实现call()方法,该方法将作为线程执行体,且call()方法有返回值。
(2)创建Callable实现类实例,使用FutureTask类包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值。
(3) 创建FutureTask对象作为Thread对象的target,创建线程对象,此时线程处于其5种状态中的新建状态。
(4)如果启动线程对象,调用其start()方法,此时线程处于其5种状态中的就绪态(此处属于线程启动部分,参见线程启动方式)。 

(5)调用FutureTask对象的get()方法获得子线程执行结束后的返回值。


3)步骤演示

//1.创建Callable接口实现类,并实现call()方法,该方法将作为线程执行体,且call()方法有返回值。class ThirdWayThread implements Callable<Integer>{public Integer call() throws Exception {int i = 0;for(; i < 23; i++){//Thread中getName()方法获取当前线程名字,此类为其子类,故也可直接调用getName()方法。System.out.println(Thread.currentThread().getName()+" 循环变量i的值 " + i);}//执行结束后返回结果return i;}}public class ThirdWayThreadTest {public static void main(String[] args) {//2.创建Callable实现类实例,使用FutureTask类包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值。ThirdWayThread twt = new ThirdWayThread();//3.创建FutureTask对象作为Thread对象的target,创建线程对象,此时线程处于其5种状态中的新建状态。FutureTask<Integer> task = new FutureTask<Integer>(twt);//4.如果启动线程对象,调用其start()方法,此时线程处于其5种状态中的就绪态(此处属于线程启动部分,参见线程启动方式)。 new Thread(task).start();try{//5.调用FutureTask对象的get()方法获得子线程执行结束后的返回值。System.out.println("子线程返回值:"+task.get());}catch(Exception e){e.printStackTrace();}}}

4.线程启动方式


前面3种方法最终都要落实到new Thread对象,因为线程想要执行就必须要有Thread对象,此时的线程状态为新建状态;如果想要线程执行就必须转换其状态为就绪态(等待CPU调度),此时就需要调用其.start()方法。关于此会在下篇文章中有更为显式的表达。


5.  3种线程创建方式对比


实现Runnable接口或实现Callable接口方式基本相同,只是Callable接口里定义的方法有返回值,可以声明抛出异常而已,因此可以把两种方式归结为一种,下面就对比继承Thread类或实现两种接口创建线程的优缺点。

1)继承Thread

(1)优点

a.代码编写简单。

b.如果需要访问当前线程,无须使用Thread.currentThread()方法,直接使用this即可获取当前线程。


(2)缺点

a.线程类继承了Thread类,不能再继承其他类,有局限性。

b.把业务模型与具体运转的线程模型混杂在一起,不利于解耦。


2)实现Runnable或Callable


(1)优点

a.完成业务代码与线程类的解耦,线程类都是转换业务类的载体而已。
b.业务类实现Runnable接口或Callable接口,还可继承其它类(这也是接口出现的原因,解决单继承带来弊端)。

c.此方式下,多个线程共享同一target对象,非常适合多个相同线程处理同一资源,形成了运转模型(线程)、业务模型、资源模型清晰的模型,更好体现面向对象。


(2)缺点

a.代码实现复杂。

b.访问当前线程必须使用Thread.currentThread()方法,当然这也是代码实现复杂的原因之一。

综上,一般使用实现Runnable接口或Callable 接口的方式来创建多线程。

0 0