本文介绍Java中如何创建线程。
目录:
- Runnable-定义无返回值的任务
- Thread-线程构造器
- Callable-定义可以返回值的任务
- 总结
在Java中,创建新执行线程有四种方法。
- 继承Thread。
- 实现Runnable。
- 实现Callable。
我认为Runnable和Callable的作用只是定义任务,创建线程还是需要Thread构造器。
Runnable
线程可以驱动任务,所以我们需要定义任务,Runnable可以来实现这个需求。
Runnable是一个接口,其中只声明了一个run方法。
public interface Runnable {
public abstract void run();
}
要想定义任务,只需要实现Runnable接口,并实现run方法。
下面通过实例看下如何通过实现Runnable创建和启动线程。
例1:
//定义任务
class MyThread2 implements Runnable {
// 重写run方法
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + "在运行");
}
}
}
public class MyRunnableTest {
public static void main(String[] args) {
// 创建任务
MyThread2 mt= new MyThread2();
//将任务附着在线程上
Thread t1 = new Thread(mt);
Thread t2 = new Thread(mt);
// 启动线程
t1.start();
t2.start();
}
}
通过实现Runnable接口来定义的任务并无线程能力。要实现线程能力,必须显示的将一个任务附着到线程上。
运行结果为:
Thread-1在运行
Thread-0在运行
Thread-0在运行
Thread-1在运行
Thread-0在运行
Thread-1在运行
Thread
Thread是一个类,它实现了Runnable,并额外提供了一些方法供用户使用。它的定义如下:
public class Thread implements Runnable {...}
在我看来,Thread就是一个线程构造器。
下面通过实例看下如何通过继承Thread创建和启动线程。
例2:
//定义线程类
class MyThread extends Thread {
//重写run方法
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(this.getName() + "在运行");
}
}
};
public class MyThreadTest {
public static void main(String[] args) {
// 创建线程
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
//启动线程
t1.start();
t2.start();
for (int i = 0; i < 3; i++)
{
System.out.println(Thread.currentThread().getName() + "在运行");
}
}
}
运行结果为:
Thread-1在运行
main在运行
Thread-0在运行
main在运行
Thread-1在运行
main在运行
Thread-0在运行
Thread-0在运行
Thread-1在运行
Thread-0和Thread-1是我们创建的线程,因为我们没有给它们命名,所以JVM为它们分配了两个名字。名字为main的线程是main方法所在的线程,也是主线程。主线程的名字总是mian,非主线程的名字不确定。从执行结果中可以看到,几个线程不是顺序执行的,JVM和操作系统一起决定了线程的执行顺序。所以,你运行后可能看到的不是这样的打印顺序。
Callable
Callable接口类似于Runnable,两者作用都是定义任务。不同的是,被线程执行后,Callable可以返回结果或抛出异常。而Runnable不可以。Callable的返回值可以通过Future来获取。Callable的定义如下:
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
下面通过实例看下如何通过实现Callable创建和启动线程并获取返回值。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//定义任务
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "This is returned string.";
}
}
public class MyCallabeTest {
public static void main(String[] args){
//创建有返回值的任务
Callable<String> callable = new MyCallable();
FutureTask<String> task = new FutureTask<>(callable);
// 创建并启动线程
new Thread(task).start();
String result = null;
try {
// 调用get()阻塞主线程,并获取返回值
result = task.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
System.out.println("return : " + result);
}
}
运行main方法后打印结果为return : This is returned string.
。
FutureTask实现了Runnable和Callable接口,所以它既可以作为任务附加到线程上(new Thread(task).start();
),又可以作为Future获取Callable的返回值(result = task.get();
)。
下面介绍另一种使用Callable和Future的方法。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class MyCallabeTest2 {
public static void main(String[] args){
//创建有返回值的任务。Callable使用第一个例子中的MyCallable
Callable<String> callable = new MyCallable();
//创建线程执行器
ExecutorService threadPool = Executors.newSingleThreadExecutor();
//通过线程执行器执行callable,并通过future获取返回结果
Future<String> future = threadPool.submit(callable);
String result = null;
try {
// 调用get()阻塞主线程,并获取返回值
result = future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally{
threadPool.shutdown();
}
System.out.println("return : " + result);
}
}
运行main方法后打印结果为return : This is returned string.
。
Java SE5的Java.util.concurrent包中的Executor(执行器)可以为我们管理线程,从而简化了并发编程。ExecutorService继承自Executor。下面会详细讲执行器。
下面介绍一种使用Callable和Future的获取多个返回值的方法。
import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
//定义任务
class MyCallable3 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return new Random().nextInt(100);
}
}
public class MyCallabeTest3 {
public static void main(String[] args){
//创建线程执行器
ExecutorService threadPool = Executors.newSingleThreadExecutor();
//创建Future类型的集合
ArrayList<Future<Integer>> results = new ArrayList<Future<Integer>>();
//将Executor提交的任务的返回值添加到集合中
for(int i = 0;i<5;i++)
results.add(threadPool.submit(new MyCallable3()));
//遍历集合取出数据
try {
// 调用get()阻塞主线程,并获取返回值
for(Future result:results)
System.out.println(result.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally{
threadPool.shutdown();
}
}
}
运行main方法后打印结果为
4
4
94
63
11
注释已经写得很清楚了,这里不再多说。
线程池
在上面的例子中使用了线程执行器,也就是线程池。有人说线程池也是创建线程的一种方式,但看过线程池的源码就知道,线程池创建线程执行任务也是依赖于Thread的。
总结
Thread和Runnable该如何选择?
- 因为Java不支持多继承,但支持多实现。所以从这个方面考虑Runnable比Thread有优势。
- Thread中提供了一些基本方法。而Runnable中只有run方法。如果只想重写run()方法,而不重写其他Thread方法,那么应使用Runnable接口。除非打算修改或增强Thread类的基本行为,否则应该选择Runnable。
从上面的分析可以看到,一般情况下Runnable更有优势。
run方法与start方法的区别
启动线程的方法是start方法。线程t启动后,t从新建状态转为就绪状态, 但并没有运行。 t获取CPU权限后才会运行,运行时执行的方法就是run方法。此时有t和主线程两个线程在运行,如果t阻塞,可以直接继续执行主线程中的代码。
直接运行run方法也是合法的,但此时并没有新启动一个线程,run方法是在主线程中执行的。此时只有主线程在运行,必须等到run方法中的代码执行完后才可以继续执行主线程中的代码。
可以将例2中代码t1.start();
改为t1.run()
,t2.start();
改为t2.run()
,你会发现打印结果会变为
Thread-0在运行
Thread-0在运行
Thread-0在运行
Thread-1在运行
Thread-1在运行
Thread-1在运行
main在运行
main在运行
main在运行
说明执行顺序为t1、t2、主线程(main)?不!this.getName()
并不能代表当前运行的线程名,只有Thread.currentThread().getName()
才能代表。可以将例2中代码this.getName()
改为Thread.currentThread().getName()
,会发现打印结果变为:
main在运行
main在运行
main在运行
main在运行
main在运行
main在运行
main在运行
main在运行
main在运行
这说明run方法根本就没有启动线程,从始至终运行的都只是主线程。
线程只能被启动一次
一个线程一旦被启动,它就不能再次被启动。在例2中在代码t1.start();
后再加一句t1.start();
,再运行会抛出java.lang.IllegalThreadStateException。
本文就讲到这里,想了解更多内容请参考
- Java并发编程札记-目录
END.