目录
继承Thread类创建线程
public class MultiThreads {
public static void main(String[] args) throws InterruptedException {
System.out.println(Thread.currentThread().getName());
System.out.println("继承Thread类创建线程");
SubClassThread subClassThread = new SubClassThread();
subClassThread.start();
}
}
class SubClassThread extends Thread {
@Override
public void run() {
System.out.println(getName());
}
}
输出结果: SubClassThread是一个继承了Thread类的子类,继承Thread类,并重写其中的run方法。然后new 一个SubClassThread的对象,并调用其start方法,即可启动一个线程。之后就会运行run中的代码。
每个线程都是通过某个特定Thread对象所对应的方法run()
来完成其操作的,方法run()
称为线程体。通过调用Thread类的start()
方法来启动一个线程。
在主线程中,调用了子线程的start()
方法后,主线程无需等待子线程的执行,即可执行后续的代码。而子线程便会开始执行其run()
方法。
当然,run()
方法也是一个公有方法,在main函数中也可以直接调用这个方法,但是直接调用run()
的话,主线程就需要等待其执行完,这种情况下,run()
就是一个普通方法。
如果读者感兴趣的话,查看一下Thread的源码,就可以发现,他继承了一个接口,那就是java.lang.Runnable
,其实,开发者在代码中也可以直接通过这个接口创建一个新的线程。
实现Runnable接口创建线程
public class MultiThreads {
public static void main(String[] args) throws InterruptedException {
System.out.println(Thread.currentThread().getName());
System.out.println("实现Runnable接口创建线程");
RunnableThread runnableThread = new RunnableThread();
new Thread(runnableThread).start();
}
}
class RunnableThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
输出结果:
通过实现接口,同样覆盖run()
就可以创建一个新的线程了。
我们都知道,Java是不支持多继承的,所以,使用Runnbale接口的形式,就可以避免要多继承 。比如有一个类A,已经继承了类B,就无法再继承Thread类了,这时候要想实现多线程,就需要使用Runnable接口了。
除此之外,两者之间几乎无差别。
但是,这两种创建线程的方式,其实是有一个缺点的,那就是:在执行完任务之后无法获取执行结果。
如果我们希望再主线程中得到子线程的执行结果的话,就需要用到Callable和FutureTask
通过Callable和FutureTask创建线程
自从Java 1.5开始,提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果。
public class MultiThreads {
public static void main(String[] args) throws InterruptedException {
CallableThread callableThread = new CallableThread();
FutureTask futureTask = new FutureTask<>(callableThread);
new Thread(futureTask).start();
System.out.println(futureTask.get());
}
class CallableThread implements Callable {
@Override
public Object call() throws Exception {
System.out.println(Thread.currentThread().getName());
return "Hollis";
}
}
输出结果:
main
通过Callable和FutureTask创建线程
Thread-2
Hollis
Callable位于java.util.concurrent包下,它也是一个接口,在它里面也只声明了一个方法,只不过这个方法call(),和Runnable接口中的run()方法不同的是,call()方法有返回值。
以上代码中,我们在CallableThread的call方法中返回字符串"Hollis",在主线程是可以获取到的。
FutureTask可用于异步获取执行结果或取消执行任务的场景。通过传入Callable的任务给FutureTask,直接调用其run方法或者放入线程池执行,之后可以在外部通过FutureTask的get方法异步获取执行结果,因此,FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
另外,FutureTask还可以确保即使调用了多次run方法,它都只会执行一次Runnable或者Callable任务,或者通过cancel取消FutureTask的执行等。
值得注意的是,futureTask.get()
会阻塞主线程,一直等子线程执行完并返回后才能继续执行主线程后面的代码。
一般,在Callable执行完之前的这段时间,主线程可以先去做一些其他的事情,事情都做完之后,再获取Callable的返回结果。可以通过isDone()
来判断子线程是否执行完。
以上代码改造下就是如下内容:
public class MultiThreads {
public static void main(String[] args) throws InterruptedException {
CallableThread callableThread = new CallableThread();
FutureTask futureTask = new FutureTask<>(callableThread);
new Thread(futureTask).start();
System.out.println("主线程先做其他重要的事情");
if(!futureTask.isDone()){
// 继续做其他事儿
}
System.out.println(future.get()); // 可能会阻塞等待结果
}
一般,我们会把Callable放到线程池中,然后让线程池去执行Callable中的代码。关于线程池前面介绍过了,是一种避免重复创建线程的开销的技术手段,线程池也可以用来创建线程。
通过线程池创建线程
Java中提供了对线程池的支持,有很多种方式。Jdk提供给外部的接口也很简单。直接调用ThreadPoolExecutor构造一个就可以了:
public class MultiThreads {
public static void main(String[] args) throws InterruptedException, ExecutionException {
System.out.println(Thread.currentThread().getName());
System.out.println("通过线程池创建线程");
ExecutorService executorService = new ThreadPoolExecutor(1, 1, 60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(10));
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
}
输出结果:
所谓线程池本质是一个hashSet。多余的任务会放在阻塞队列中。
线程池的创建方式其实也有很多,也可以通过Executors静态工厂构建,但一般不建议。建议使用线程池来创建线程,并且建议使用带有ThreadFactory参数的ThreadPoolExecutor(需要依赖guava)构造方法设置线程名字,具体原因我们在后面的章节中在详细介绍。
每日寄语
获得真正自由的方法是要学会自我控制。如果情绪总是处于失控状态,就会被感情牵着鼻子走,丧失自由。所以那些精神自由,保持独立思考的人也正是擅长于控制自己情绪的人。——尼采