zoukankan      html  css  js  c++  java
  • Java并发编程:Java实现多线程的几种方式

    在Java中,多线程主要的实现方式有四种:继承Thread类、实现Runnable接口、实现Callable接口通过FutureTask包装器来创建Thread线程、使用ExecutorService、Callable、Future实现有返回结果的多线程。其中前两种方式线程执行完后都没有返回值,而后两种是带返回值的。除此之外,通过Timer启动定时任务,或者通过像Spring Task和quartz这样的第三方任务调度框架也可以开启多线程任务。

    1、继承Thread类创建线程

    Thread类本质上也是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程比较简单,通过继承Thread类并复写run()方法,就可以启动新线程并执行自己定义的run()方法。

    CreateThreadDemo1.java

    public class CreateThreadDemo1 extends Thread {
    
        public CreateThreadDemo1(String name) {
            // 设置当前线程的名字
            this.setName(name);
        }
    
        @Override
        public void run() {
            System.out.println("当前运行的线程名为: " + Thread.currentThread().getName());
        }
    
        public static void main(String[] args) throws Exception {
            // 注意这里,要调用start方法才能启动线程,不能调用run方法
            new CreateThreadDemo1("MyThread1").start();
            new CreateThreadDemo1("MyThread2").start();
    
        }
    
    }

    输出结果:

    当前运行的线程名为: MyThread1
    当前运行的线程名为: MyThread2

    2、实现Runnable接口创建线程
    由于Java是单继承机制,如果自己的类已经继承自另一个类,则无法再直接继承Thread类,此时,可以通过实现Runnable接口来实现多线程。

    实现Runnable接口并实现其中的run方法,然后通过构造Thread实例,传入Runnable实现类,然后调用Thread的start方法即可开启一个新线程。

    CreateThreadDemo2.java

    public class CreateThreadDemo2 implements Runnable {
    
        @Override
        public void run() {
            System.out.println("当前运行的线程名为: " + Thread.currentThread().getName());
        }
    
        public static void main(String[] args) throws Exception {
            CreateThreadDemo2 runnable = new CreateThreadDemo2();
            new Thread(runnable, "MyThread1").start();
            new Thread(runnable, "MyThread2").start();
    
        }
    
    }

    输出结果:

    当前运行的线程名为: MyThread1
    当前运行的线程名为: MyThread2

    3、实现Callable接口通过FutureTask包装器来创建Thread线程

    首先需要一个实现Callable接口的实例,然后实现该接口的唯一方法call逻辑,接着把Callable实例包装成FutureTask传递给Thread实例启动新线程。FutureTask本质上也实现了Runnable接口,所以同样可以用来构造Thread实例。

    CreateThreadDemo3.java

    import java.util.concurrent.Callable;
    import java.util.concurrent.FutureTask;
    
    public class CreateThreadDemo3 {
    
        public static void main(String[] args) throws Exception {
            // 创建线程任务,lambada方式实现接口并实现call方法
            Callable<Integer> callable = () -> {
                System.out.println("线程任务开始执行了...");
                Thread.sleep(2000);
                return 1;
            };
    
            // 将任务封装为FutureTask
            FutureTask<Integer> task = new FutureTask<>(callable);
    
            // 开启线程,执行线程任务
            new Thread(task).start();
    
            // ====================
            // 这里是在线程启动之后,线程结果返回之前
            System.out.println("线程启动之后,线程结果返回之前...");
            // ====================
    
            // 为所欲为完毕之后,拿到线程的执行结果
            Integer result = task.get();
            System.out.println("主线程中拿到异步任务执行的结果为:" + result);
    
        }
    
    }

    输出结果:

    线程启动之后,线程结果返回之前...
    线程任务开始执行了...
    主线程中拿到异步任务执行的结果为:1

    4、使用ExecutorService、Callable、Future实现有返回结果的线程(线程池方式)

    ExecutorService、Callable、Future三个接口都是属于Executor框架。可返回值的任务必须实现Callable接口。通过ExecutorService执行Callable任务后,可以获取到一个Future的对象,在该对象上调用get()就可以获取到Callable任务返回的结果了。

    注意:Future的get方法是阻塞的,即:线程无返回结果,get方法会一直等待。

    CreateThreadDemo4.java

    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;
    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 CreateThreadDemo4 {
        
        @SuppressWarnings({ "rawtypes", "unchecked" })
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            System.out.println("---- 主程序开始运行 ----");
            Date startTime = new Date();
            
            int taskSize = 5;
            // 创建一个线程池,Executors提供了创建各种类型线程池的方法,具体详情请自行查阅
            ExecutorService executorService = Executors.newFixedThreadPool(taskSize);
            
            // 创建多个有返回值的任务
            List<Future> futureList = new ArrayList<Future>();
            for (int i = 0; i < taskSize; i++) {
                Callable callable = new MyCallable(i);
                // 执行任务并获取Future对象
                Future future = executorService.submit(callable);
                futureList.add(future);
            }
            
            // 关闭线程池
            executorService.shutdown();
    
            // 获取所有并发任务的运行结果
            for (Future future : futureList) {
                // 从Future对象上获取任务的返回值,并输出到控制台
                System.out.println(">>> " + future.get().toString());
            }
    
            Date endTime = new Date();
            System.out.println("---- 主程序结束运行 ----,程序运行耗时【" + (endTime.getTime() - startTime.getTime()) + "毫秒】");
        }
    }
    
    class MyCallable implements Callable<Object> {
        private int taskNum;
    
        MyCallable(int taskNum) {
            this.taskNum = taskNum;
        }
    
        public Object call() throws Exception {
            System.out.println(">>> " + taskNum + " 线程任务启动");
            Date startTime = new Date();
            Thread.sleep(1000);
            Date endTime = new Date();
            long time = endTime.getTime() - startTime.getTime();
            System.out.println(">>> " + taskNum + " 线程任务终止");
            return taskNum + "线程任务返回运行结果, 当前任务耗时【" + time + "毫秒】";
        }
    }

    输出结果:

    ---- 主程序开始运行 ----
    >>> 0 线程任务启动
    >>> 1 线程任务启动
    >>> 2 线程任务启动
    >>> 3 线程任务启动
    >>> 4 线程任务启动
    >>> 0 线程任务终止
    >>> 1 线程任务终止
    >>> 0线程任务返回运行结果, 当前任务耗时【1001毫秒】
    >>> 1线程任务返回运行结果, 当前任务耗时【1001毫秒】
    >>> 4 线程任务终止
    >>> 3 线程任务终止
    >>> 2 线程任务终止
    >>> 2线程任务返回运行结果, 当前任务耗时【1001毫秒】
    >>> 3线程任务返回运行结果, 当前任务耗时【1001毫秒】
    >>> 4线程任务返回运行结果, 当前任务耗时【1001毫秒】
    ---- 主程序结束运行 ----,程序运行耗时【1009毫秒】

    5、其他创建线程的方式

    当然,除了以上四种主要的线程创建方式之外,也还有很多其他的方式可以启动多线程任务。比如通过Timer启动定时任务,或者通过像Spring Task和quartz这样的第三方任务调度框架也可以开启多线程任务,关于第三方任务调度框架的例子还请查询相关资料。

    源码下载

    码云:https://gitee.com/liuge1988/java-demo.git


    作者:朝雨忆轻尘
    出处:https://www.cnblogs.com/xifengxiaoma/ 
    版权所有,欢迎转载,转载请注明原文作者及出处。

  • 相关阅读:
    c/c++字符串传递
    从一个小程序明白new和delete的奇特现象
    Linux下构造函数string需要注意的问题
    字符串转time_t
    CentOS7基础建站指南(笔记)
    图与搜索
    面向的对象编程的小猫腻
    多线程编程
    生产者消费者模式-Java实现
    Java-代理模式的理解
  • 原文地址:https://www.cnblogs.com/xifengxiaoma/p/11473775.html
Copyright © 2011-2022 走看看