简介
创建线程, 是多线程编程中最基本的操作, 彤哥总结了一下, 大概有 8 种创建线程的方式, 你知道吗?
继承 Thread 类并重写 run() 方法
- public class CreatingThread01 extends Thread {
- @Override
- public void run() {
- System.out.println(getName() + "is running");
- }
- public static void main(String[] args) {
- new CreatingThread01().start();
- new CreatingThread01().start();
- new CreatingThread01().start();
- new CreatingThread01().start();
- }
- }
继承 Thread 类并重写 run() 方法, 这种方式的弊端是一个类只能继承一个父类, 如果这个类本身已经继承了其它类, 就不能使用这种方式了.
实现 Runnable 接口
- public class CreatingThread02 implements Runnable {
- @Override
- public void run() {
- System.out.println(Thread.currentThread().getName() + "is running");
- }
- public static void main(String[] args) {
- new Thread(new CreatingThread02()).start();
- new Thread(new CreatingThread02()).start();
- new Thread(new CreatingThread02()).start();
- new Thread(new CreatingThread02()).start();
- }
- }
实现 Runnable 接口, 这种方式的好处是一个类可以实现多个接口, 不影响其继承体系.
匿名内部类
- public class CreatingThread03 {
- public static void main(String[] args) {
- // Thread 匿名类, 重写 Thread 的 run() 方法
- new Thread() {
- @Override
- public void run() {
- System.out.println(getName() + "is running");
- }
- }.start();
- // Runnable 匿名类, 实现其 run() 方法
- new Thread(new Runnable() {
- @Override
- public void run() {
- System.out.println(Thread.currentThread().getName() + "is running");
- }
- }).start();
- // 同上, 使用 lambda 表达式函数式编程
- new Thread(()->{
- System.out.println(Thread.currentThread().getName() + "is running");
- }).start();
- }
- }
使用匿名类的方式, 一是重写 Thread 的 run() 方法, 二是传入 Runnable 的匿名类, 三是使用 lambda 方式, 现在一般使用第三种 (java8+), 简单快捷.
实现 Callabe 接口
- public class CreatingThread04 implements Callable {
- @Override
- public Long call() throws Exception {
- Thread.sleep(2000);
- System.out.println(Thread.currentThread().getId() + "is running");
- return Thread.currentThread().getId();
- }
- public static void main(String[] args) throws ExecutionException, InterruptedException {
- FutureTask task = new FutureTask<>(new CreatingThread04());
- new Thread(task).start();
- System.out.println("等待完成任务");
- Long result = task.get();
- System.out.println("任务结果:" + result);
- }
- }
实现 Callabe 接口, 可以获取线程执行的结果, FutureTask 实际上实现了 Runnable 接口.
定时器 (java.util.Timer)
- public class CreatingThread05 {
- public static void main(String[] args) {
- Timer timer = new Timer();
- // 每隔 1 秒执行一次
- timer.schedule(new TimerTask() {
- @Override
- public void run() {
- System.out.println(Thread.currentThread().getName() + "is running");
- }
- }, 0 , 1000);
- }
- }
使用定时器 java.util.Timer 可以快速地实现定时任务, TimerTask 实际上实现了 Runnable 接口.
线程池
- public class CreatingThread06 {
- public static void main(String[] args) {
- ExecutorService threadPool = Executors.newFixedThreadPool(5);
- for (int i = 0; i < 100; i++) {
- threadPool.execute(()-> System.out.println(Thread.currentThread().getName() + "is running"));
- }
- }
- }
使用线程池的方式, 可以复用线程, 节约系统资源.
并行计算 (Java8+)
- public class CreatingThread07 {
- public static void main(String[] args) {
- List list = Arrays.asList(1, 2, 3, 4, 5);
- // 串行, 打印结果为 12345
- list.stream().forEach(System.out::print);
- System.out.println();
- // 并行, 打印结果随机, 比如 35214
- list.parallelStream().forEach(System.out::print);
- }
- }
使用并行计算的方式, 可以提高程序运行的效率, 多线程并行执行.
Spring 异步方法
首先, springboot 启动类加上 @EnableAsync 注解 (@EnableAsync 是 spring 支持的, 这里方便举例使用 springboot).
- @SpringBootApplication
- @EnableAsync
- public class Application {
- public static void main(String[] args) {
- SpringApplication.run(Application.class, args);
- }
- }
其次, 方法加上 @Async 注解.
- @Service
- public class CreatingThread08Service {
- @Async
- public void call() {
- System.out.println(Thread.currentThread().getName() + "is running");
- }
- }
然后, 测试用例直接跟使用一般的 Service 方法一模一样.
- @RunWith(SpringRunner.class)
- @SpringBootTest(classes = Application.class)
- public class CreatingThread08Test {
- @Autowired
- private CreatingThread08Service creatingThread08Service;
- @Test
- public void test() {
- creatingThread08Service.call();
- creatingThread08Service.call();
- creatingThread08Service.call();
- creatingThread08Service.call();
- }
- }
运行结果如下:
- task-3 is running
- task-2 is running
- task-1 is running
- task-4 is running
可以看到每次执行方法时使用的线程都不一样.
使用 Spring 异步方法的方式, 可以说是相当地方便, 适用于前后逻辑不相关联的适合用异步调用的一些方法, 比如发送短信的功能.
总结
(1) 继承 Thread 类并重写 run() 方法;
(2) 实现 Runnable 接口;
(3) 匿名内部类;
(4) 实现 Callabe 接口;
(5) 定时器 (java.util.Timer);
(6) 线程池;
(7) 并行计算 (Java8+);
(8)Spring 异步方法;
彩蛋
上面介绍了那么多创建线程的方式, 其实本质上就两种, 一种是继承 Thread 类并重写其 run() 方法, 一种是实现 Runnable 接口的 run() 方法, 那么它们之间到底有什么联系呢?
请看下面的例子, 同时继承 Thread 并实现 Runnable 接口, 应该输出什么呢?
- public class CreatingThread09 {
- public static void main(String[] args) {
- new Thread(()-> {
- System.out.println("Runnable:" + Thread.currentThread().getName());
- }) {
- @Override
- public void run() {
- System.out.println("Thread:" + getName());
- }
- }.start();
- }
- }
说到这里, 我们有必要看一下 Thread 类的源码:
- public class Thread implements Runnable {
- // Thread 维护了一个 Runnable 的实例
- private Runnable target;
- public Thread() {
- init(null, null, "Thread-" + nextThreadNum(), 0);
- }
- public Thread(Runnable target) {
- init(null, target, "Thread-" + nextThreadNum(), 0);
- }
- private void init(ThreadGroup g, Runnable target, String name,
- long stackSize, AccessControlContext acc,
- boolean inheritThreadLocals) {
- // ...
- // 构造方法传进来的 Runnable 会赋值给 target
- this.target = target;
- // ...
- }
- @Override
- public void run() {
- // Thread 默认的 run() 方法, 如果 target 不为空, 会执行 target 的 run() 方法
- if (target != null) {
- target.run();
- }
- }
- }
看到这里是不是豁然开朗呢? 既然上面的例子同时继承 Thread 并实现了 Runnable 接口, 根据源码, 实际上相当于重写了 Thread 的 run() 方法, 在 Thread 的 run() 方法时实际上跟 target 都没有关系了.
所以, 上面的例子输出结果为 Thread: Thread-0, 只输出重写 Thread 的 run() 方法中的内容.
来源: http://developer.51cto.com/art/201910/604014.htm