上一篇:进程和线程的基本概念
线程的创建有三种方法:继承Thread、实现Runnable接口、使用Callable和Future.
步骤如下:
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()类的静态方法,该方法返回当前正在执行的线程对象。
步骤如下:
注: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类)的实例变量。
上面已经指出,通过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使用。
步骤如下:
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接口定义了如下几个共有方法:
采用Runnable、Callable接口实现多线程:
采用继承Thread类实现:
综上,一般推荐使用实现接口的方式来创建多线程。