这是我参与8月更文挑战的第20天,活动详情查看:8月更文挑战
前言
面试中在涉及到多线程这一块的时候,或多或少都被问到过线程的创建方式,如果平时不留意,一时真不知道该怎么回答.借这次学习机会,梳理一下。
创建线程的三种方式
- 继承
Thread类创建线程- 实现
Runnable接口创建线程- 使用
Callable和Future创建线程
继承Thread
public class ThreadDemo_1 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
MyThread myThread2 = new MyThread();
myThread.start();
myThread2.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(this.getName() + ":" + i);
}
}
}
打印输出
Thread-1:0
Thread-1:1
Thread-1:2
Thread-0:0
Thread-1:3
Thread-0:1
Thread-1:4
Thread-0:2
Thread-0:3
Thread-0:4
可以看到两个线程都完成了打印任务,并且交替打印互不影响
实现Runnable
public class ThreadDemo_2 {
public static void main(String[] args) {
MyThread02 myThread = new MyThread02();
Thread thread01 = new Thread(myThread);
Thread thread02 = new Thread(new MyThread02());
thread01.start();
thread02.start();
}
}
class MyThread02 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
打印输出
Thread-0:0
Thread-0:1
Thread-0:2
Thread-0:3
Thread-1:0
Thread-0:4
Thread-1:1
Thread-1:2
Thread-1:3
Thread-1:4
这里要注意的是在实现
Runnable方法里是不能通过this.getName()获取当前线程的名称,通过Thread.currentThread().getName()去获取
使用Callable和Future
- 在
JDK1.5以后为我们专门提供了一个并发工具包java.util.concurrent.JUC包下包含许多线程安全、测试良好、高性能的并发结构模块。创建concurrent的目的就是要实现Collection框架对数据结构所执行的并发操。通过提供一组可靠的,高性能并发构建块,开发人员可以提高并发类的线程安全、可伸缩性、性能、可读性和可靠性。简单说GUC专为并发而生的。
public class ThreadDemo_3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建一个线程池。里面默认有3个空线程。
// Executors是调度器,对线程池进行管理
ExecutorService executorService = Executors.newFixedThreadPool(3);
MyThread03 myThread01 = new MyThread03();
MyThread03 myThread02 = new MyThread03();
// 将这个对象扔到线程池里,线程池会自动分给一个线程来运行这个对象的call方法
// Future用于接收线程内部call方法的返回值
Future<Integer> submit = executorService.submit(myThread01);
Future<Integer> submit2 = executorService.submit(myThread02);
// 关闭线程池释放所有资源
executorService.shutdown();
System.out.println("submit all返回值:" + submit.get());
System.out.println("submit2 call返回值:" + submit2.get());
}
}
class MyThread03 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int count = 0;
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
count++;
}
return count;
}
}
打印输出
pool-1-thread-2:0
pool-1-thread-1:0
pool-1-thread-2:1
pool-1-thread-1:1
pool-1-thread-2:2
pool-1-thread-1:2
pool-1-thread-2:3
pool-1-thread-2:4
pool-1-thread-1:3
pool-1-thread-1:4
submit all返回值:5
submit2 call返回值:5
从打印可以看出,这里引用了线程池的概念,线程池中的线程通过接收到发布的任务,然后去完成对应的事情。
三种方式优缺点
继承Thread
- 优点: 编程简单,执行效率高
- 缺点: 单继承,无法对线程组有效控制
- 使用背景: 不推荐使用 实现Runnable
- 优点: 面向接口编程,执行效率高
- 缺点: 无法对线程组有效控制,没有返回值、异常
- 使用背景: 简单的多线程程序 利用线程池
- 优点: 容器管理线程,允许返回值与异常
- 缺点: 执行效率相对低,编程麻烦
- 使用背景: 企业级应用 推荐使用
总结
实现Runnable和Callable的区别
- 实现了
Runnable接口后无法返回结果信息,实现了Callable接口后有返回值。- 实现了
Runnable接口异常无法通过throws抛出异常,实现了Runnable接口后可以直接抛出Exception异常