文章目录
1. 创建线程
Java中创建线程的方式有两种:
- 实现
java.lang.Runnable
接口 - 继承
java.lang.Thread
类
1.1 通过Runnable接口创建线程
public class MyRunnable implements Runnable {
@Override
public void run() {
Printer.print("MyRunnable.run() start. start time = " + Timer.getTime());
Printer.print("current thread id = " + Thread.currentThread().getId());
try {
Thread.sleep(5000);
} catch (InterruptedException e){
e.printStackTrace();
}
Printer.print("MyRunnable.run() finished. finish time = " + Timer.getTime());
}
}
1.2 通过Thread类创建线程
public class MyThread extends Thread{
@Override
public void run() {
Printer.print("MyThread.run() start. start time = " + Timer.getTime());
Printer.print("current thread id = " + Thread.currentThread().getId());
super.run();
try {
sleep(2000);
} catch (InterruptedException e){
e.printStackTrace();
}
Printer.print("MyThread.run() finished. finish time = " + Timer.getTime());
}
}
1.3 实例说明
实例化 1.1 和 1.2 中创建的线程并调用其执行方法
public static void main(String[] args){
MyThread thread = new MyThread();
MyRunnable runnable = new MyRunnable();
thread.start();
runnable.run();
}
输出的内容如下:
MyRunnable.run() start. start time = 2019-01-26 12:11:18
current thread id = 1
MyThread.run() start. start time = 2019-01-26 12:11:18
current thread id = 11
MyThread.run() finished. finish time = 2019-01-26 12:11:20
MyRunnable.run() finished. finish time = 2019-01-26 12:11:23
可以看到MyThread和MyRunnable并不是一个执行完再执行另一个,而是同时执行的,并且两个任务所在的线程id也不一样,可以证明确实是在不同的线程中执行。
Thread 和 Runnable 的区别是显而易见的,它们一个是类一个是接口,实际使用中选择哪一个来创建新线程则要看具体需求。
2. 返回结果的线程任务 Callable 和 Future
假如我们我执行一个多线程任务,并希望任务完成之后返回结果值,这时候用Thread或Runnable就无法实现这个目的,因为它们只能用来执行任务,无法返回值。而Callable和Future就是为了实现返回值而设计的,下面就来详细介绍它们的用法。
2.1 Callable接口
2.1.1 Callable与Runnable的区别
- 实现Callable接口需实现其call()方法,call()方法会返回一个结果值,Runnable接口的run()方法不会返回值
- Callable接口并不能用来创建一个新线程,Runnable接口可以
- Callable接口的call()方法可能会抛出异常,而Runnable接口的run()方法不会
2.1.2 实现Callable接口的例子
public class MyCallable implements Callable<String> {
private int sleepTime;
public MyCallable(int sleepTime) {
this.sleepTime = sleepTime;
}
@Override
public String call() {
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e){
e.printStackTrace();
}
return Thread.currentThread().getName() + " : " + Timer.getTime();
}
}
Callable接口接受一个类型参数,指定call()方法的返回值类型。如果不指定,call()方法的返回值类型默认为Object。
2.2 Future接口
call()方法完成之后,结果需要被保存在一个存在于主线程的对象之中,以让主线程获取结果。要实现这一目标,需要使用Future。Future用来保存call()方法返回的结果,有了Future,就能在主线程中观测其它线程的任务执行进度并获取其返回值。
要实现Future接口,需要重写它的以下5个方法:
-
boolean cancel(boolean mayInterrupt)
这个方法用来取消任务,如果任务尚未开始,它将直接终止任务。如果任务已经开始,它将根据mayInterrupt
来决定是否终止任务。 -
Object get() throws InterruptedException, ExecutionException
用来获取任务的结果值,如果任务已经完成,它将立即返回结果值,否则它将阻塞线程,直到任务完成并返回结果值。 -
Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
这个方法的目的和get()一样,不同之处在于它接受一个超时时长,表示其最大阻塞时长,如果时间到了还没有返回结果,它将抛出TimeoutException异常。 -
boolean isDone()
如果任务完成,或者以其它任何方式终止(比如抛了异常),都将返回true,否则返回false。 -
boolean isCanceled()
如果任务在完成之前被取消了,将返回true。
2.2.1 FutureTask
Java已经为我们封装好了一个实现类FutureTask,它同时实现了Runnable接口和Future接口,可以直接拿来用,不用自己去实现Future接口了。
写一个用FutureTask实现获取其它线程任务执行结果的例子:
private static void testFutureTask() {
MyCallable callable1 = new MyCallable(1000);
MyCallable callable2 = new MyCallable(2000);
//FutureTask初始化的时候接受Callable作为参数
FutureTask<String> futureTask1 = new FutureTask<>(callable1);
FutureTask<String> futureTask2 = new FutureTask<>(callable2);
// ExecutorService executor = Executors.newFixedThreadPool(2);
// executor.execute(futureTask1);
// executor.execute(futureTask2);
// 新开线程执行FutureTask任务,也可以使用上面的线程池方式
new Thread(futureTask1).start();
new Thread(futureTask2).start();
while (true) {
try {
if (futureTask1.isDone() && futureTask2.isDone()) {
System.out.println("Done");
//shut down executor service//
//executor.shutdown();
return;
}
if (!futureTask1.isDone()) {
//wait indefinitely for future task to complete
System.out.println("FutureTask1 output=" + futureTask1.get());
}
System.out.println("Waiting for FutureTask2 to complete");
String s = futureTask2.get(200L, TimeUnit.MILLISECONDS);
if (s != null) {
System.out.println("FutureTask2 output=" + s);
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
//do anything
}
}
}
执行上面的代码,打印的内容如下:
FutureTask1 output=Thread-0 : 2019-01-26 18:02:08
Waiting for FutureTask2 to complete
Waiting for FutureTask2 to complete
Waiting for FutureTask2 to complete
Waiting for FutureTask2 to complete
Waiting for FutureTask2 to complete
FutureTask2 output=Thread-1 : 2019-01-26 18:02:09
Done
3. 定时线程任务 Timer 和 TimerTask
TimerTask 是一个实现了 Runnable接口的抽象类。Timer则可以创建一个新线程,来定时执行任务,或者重复执行任务,用起来非常简单。
先看一个小例子:
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
Printer.print("msg from timer task: " + TimeUtil.getTime());
}
}, 0, 1000);
这段代码使用Timer每隔一秒钟重复输出一句话,输出结果如下:
msg from timer task: 2019-01-30 16:50:49
msg from timer task: 2019-01-30 16:50:50
msg from timer task: 2019-01-30 16:50:51
msg from timer task: 2019-01-30 16:50:52
msg from timer task: 2019-01-30 16:50:53
msg from timer task: 2019-01-30 16:50:54
下面介绍下Timer的几个方法:
- public void schedule(TimerTask task, long delay) : 用来延时执行任务
- public void schedule(TimerTask task, Date time) : 用来定时执行任务
- public void schedule(TimerTask task, long delay, long period) : 重复执行一个任务
- public void schedule(TimerTask task, Date firstTime, long period) : 重复执行一个任务
- public void scheduleAtFixedRate(TimerTask task, long delay, long period) : 时间控制更加精准
- public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) : 时间控制更加精致