您的位置:首页 > 编程语言 > Java开发

Java--线程的创建和启动

2018-02-26 00:00 197 查看
上一篇:进程和线程的基本概念

线程的创建有三种方法:继承Thread、实现Runnable接口、使用Callable和Future.

1、继承Thread类创建线程类

步骤如下:

定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。

创建Thread子类的实例,即创建了线程对象。

调用线程对象的start()方法来启动线程。

public class FirstThread extends Thread{//继承Thread类
private int i;
public void run() {
for(;i<100;i++)
System.out.println(getName()+" "+i);
}
public static void main(String[] args){
new FirstThread().start();//创建并启动第一个线程
new FirstThread().start();//创建并启动第二个线程
}
}

虽然上面程序只显式地创建了两个线程,但实际上程序有三个线程,即两个子线程和一个主线程。当Java运行时,程序至少创建一个主线程,该主线程的执行体不是由run()方法确定的,而是由main()方法确定。

使用继承Thread类的方法创建线程类时,多个线程之间无法共享线程类的实例变量。如下图所示,Thread-0线程和Thread-1线程都是从0计数开始(共享进程类的实例变量i )。



程序可以通过setName(String name)为线程设置名字,也可以通过getName()方法返回线程的名字。在默认情况下,主线程名字为main,其他线程名字依次为Thread-0、Thread-1、...、Thread-n。

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

2、实现Runnable接口创建线程类

步骤如下:

定义Runnable接口的实现类,并重写该接口的run()方法。

创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。

调用start()方法来启动线程。

注:Java 8 增加了lambda(λ)表达式,因为Runnable接口是函数式接口,所以可以直接使用lambda表达式来创建Runnable对象。

public class SecondThread implements Runnable{
private int i;
@Override
public void run() {
// TODO Auto-generated method stub
for(;i<100;i++)
System.out.println(Thread.currentThread()+" "+i);
}
public static void main(String[] args){
SecondThread st = new SecondThread();
new Thread(st,"新线程1").start();
new Thread(st,"新线程2").start();
}
}

采用Runnable接口的方式创建的多个线程可以共享线程类的实例变量。如下图所示,新线程1和新线程2是累加的关系。



Runnable对象仅仅作为Thread对象的target,Runnable实现类中的run()方法仅仅作为线程执行体。而世纪的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。正因为程序所创建的Runnable对象只是线程的target, 而多个线程可以共享同一个target,所以多个线程可以共享同一个线程类(实际上应该是线程的target类)的实例变量。

3、使用Callable和Future创建线程类

上面已经指出,通过Runnable接口创建线程时,Thread类的作用是把run()方法包装成线程执行体。那么可不可以直接把任意方法包装成线程执行体呢?Java目前不行。(C#可以)

但是从Java 5开始,Java提供了Callable接口,该接口提供一个call()方法作为线程执行体,并且可以有返回值,还可以声明抛出异常。这很像Runnable接口的增强版,因此可以想出使用Callable对象作为Thread的target。问题是:Callable接口不是Runnable的自接口,因此不能直接作为Thread的target。

Java 5同时提供了Future接口来代表Callable接口里的call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该类实现了Callable接口和Runnable接口,可以作为Thread的target使用。

步骤如下:

创建Callable接口的实现类,实现call()方法,再创建Callable实现类的实例。(Callable接口也是函数式接口,可以使用lambda表达式)

使用FutureTask类包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值。

使用FutureTask对象作为Thread对象的target,创建并启动线程。

调用FutureTask对象的gat方法来获得子线程执行结束后的返回值。

public class ThirdThread {
public static void main(String[] args) {
ThirdThread rt = new ThirdThread();
FutureTask<Integer> task = new FutureTask<Integer> (Callable<Integer>)()->{//lambda表达式
int i = 0;
for(;i<100;i++)
System.out.println(Thread.currentThread()+" "+i);
});
new Thread(task,"有返回值的线程").start();
try {
System.out.print("子线程返回的值"+task.get());
}catch(Exception ex) {
ex.printStackTrace();
}
}
}

Future接口定义了如下几个共有方法:

boolean cancel(boolean mayInterruptIfRunning):试图取消Future里关联的Callable。

V get():返回Callable任务里call()方法的返回值。调用该方法将导致程序阻塞,必须等到子程序结束后才能得到返回值。

V get(long timeout, TimeUnit unit):返回Callable任务里call()方法的返回值。该方法让程序最多阻塞timeout和unit指定的时间,如果超出时间Callable任务依然没有返回值,抛出TimeoutException异常。

boolean isCancelled():如果在Callable任务正常完成前被取消,则返回true。

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

创建线程的三种方式对比:

采用Runnable、Callable接口实现多线程:

线程类只是实现了接口,还可以继承其他类;

多个线程可以共享同一个target对象,非常适合多个相同线程来处理同一份资源的情况;

劣势是,变成稍微复杂,如果想访问当前线程,需要使用Thread.currentThread()方法。

采用继承Thread类实现:

劣势是,因为继承了Thread类,无法继承其他类;

优势是,编程简单。

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

下一篇----五态模型&线程控制

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Java 线程的创建