创建多线程常用的方法有四种,继承Thread、实现Runnable接口、使用Callable和FutureTask和线程池

1. 继承Thread类创建多线程

创建一个多线程需要执行两个步骤

  1. 继承Thread类,创建一个新的线程类
  2. 重写run()方法,将需要并发执行的业务代码编写在run()方法中

代码实现如下:

public class MultiThread1 {

    private static final int MAX_TURN = 5;
    public static String getCurrentThread() {
        return Thread.currentThread().getName();
    }

    static class CreateThread extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < MAX_TURN; i++) {
                System.out.println("线程名称为:" + getCurrentThread());
            }
            System.out.println("线程名称为:" + getCurrentThread() + "运行结束");
        }
    }

    public static void main(String[] args) {
        Thread thread = null;
        for (int i = 0; i < 2; i++) {
            thread = new CreateThread();
            thread.start();
        }
        System.out.println("线程名称为:" + getCurrentThread() + "运行结束");
    }
}

2. 实现Runnable接口创建多线程

将需要异步执行的业务逻辑代码写在Runnable实现类的run()方法中,再将Runnable实例作为target执行目标传入Thread实例,其完整步骤如下:

  1. 定义一个新类实现Runnable接口
  2. 实现Runnable接口中的run()抽象方法,将线程代码逻辑写在该run()实现方法中
  3. 通过Thread类创建线程对象,将Runnable实例作为实际参数传递给Thread类的构造器,由Thread构造器将该Runnable实例赋值给自己的target执行目标属性
  4. 调用Thread实例的start()方法启动线程
  5. 线程启动之后,线程的run()方法将被JVM执行,该run()方法将调用target属性的run()方法,从而完成Runnable实现类中业务代码逻辑的并发执行

具体代码如下:

public class MultiThread2 {

    private static final int MAX_TURN = 5;

    public static String getCurrentThread() {
        return Thread.currentThread().getName();
    }

    static class CreateThread implements Runnable {

        @Override
        public void run() {
            for (int i = 0; i < MAX_TURN; i++) {
                System.out.println("当前线程名称为:" + getCurrentThread());
            }
            System.out.println("当前线程名称为:" + getCurrentThread() + "运行结束");
        }
    }

    public static void main(String[] args) {
        Thread thread = null;
        for (int i = 0; i < 2; i++) {
            Runnable runnable = new CreateThread();
            thread = new Thread(runnable);
            thread.start();
        }
    }
}

使用Runnable创建线程目标类还有两种方法

2.1. 通过匿名类创建Runnable线程目标类

如果target实现类是一次性类,可以使用匿名实例,代码如下:

public class MultiThread3 {

    private static final int MAX_TURN = 5;
    static int threadNo = 1;

    public static void main(String[] args) {
        Thread thread = null;
        for (int i = 0; i < 2; i++) {
            thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < MAX_TURN; j++) {
                        System.out.println("多线程执行,第:" + j + "次执行!");
                    }
                    System.out.println("运行结束");
                }
            }, "RunnableThread" + threadNo++);
            thread.start();
        }
        System.out.println("运行结束");
    }
}

2.2. 使用Lambda表达式创建Runnable线程目标类

Runnable接口是一个函数式接口,在接口实现时可以使用Lambda表达式提供匿名实现,其代码实现如下:

public class MultiThread4 {

    private static final int MAX_TURN = 5;
    private static int threadNo = 1;

    public static void main(String[] args) {
        Thread thread = null;
        for (int i = 0; i < 2; i++) {
            thread = new Thread(() -> {
                for (int j = 0; j < MAX_TURN; j++) {
                    System.out.println("多线程执行,第:" + j + "次执行!");
                }
                System.out.println("多线程运行结束");
            }, "RunnableThread" + threadNo++);
            thread.start();
        }
        System.out.println("多线程运行结束");
    }
}

2.3. 实现Runnable接口创建线程目标类优缺点

缺点

  • 创建的类不是线程类,而是线程的target执行目标类,需要将其实例作为参数传入线程类的构造器,才能创建真正的线程
  • 访问当前线程的属性,不能直接访问Thread的实例方法,必须通过Thread.currentThread()获取当前线程实例

优点

  • 避免由于Java单继承带来的局限
  • 逻辑和数据更好分离,适合于同一个资源被多段业务逻辑并行处理的场景

2.4. 继承Thread和实现Runnable区别

  • 继承Thread类用于多个线程并发完成各自的任务,访问各自的数据资源
  • 实现Runnable接口用于多个线程并发完成同一个任务,访问同一份数据资源,数据共享资源需要使用原子类型或者进行线程同步控制

3. 使用Callable和FutureTask创建多线程

需要获取异步执行的返回结果时,使用Callable和FutureTask创建多线程

3.1. Callable与Runnable区别

  • Runnable的唯一抽象方法run()没有返回值,也没受检查异常的异常声明,Callable接口的call()有返回值,并且声明了受检查异常,功能更强
  • 使用Runnable创建多线程,实现Runnable接口的实例作为Thread线程实例的target来使用
  • 使用Callable创建多线程,使用RunnableFuture作为Thread线程实例的target实例和获取异步执行的结果,其实现类是FutureTask

使用Callable和FutureTask创建线程的步骤如下:

  1. 创建一个Callable接口的实现类,并实现其call()方法,编写异步执行的具体逻辑,可以有返回值
  2. 使用Callable实现类的实例构造一个FutureTask实例
  3. 使用FutrueTask实例作为Thread构造器的target入参,构造新的Thread线程实例
  4. 调用Thread实例的start()方法启动线程,启动新线程的run()方法并发执行
  5. 调用FutureTask对象的get()方法阻塞性地获取并发线程执行结果

代码实现如下:

public class MultiThread5 {
    
    private static final int COMPUTE_TIMES = 100000000;
    
    static class CreateThread implements Callable<Long> {
        @Override
        public Long call() throws Exception {
            long startTime = System.currentTimeMillis();
            System.out.println("多线程执行开始,当前时间为:" + startTime);
            Thread.sleep(1000);
            for (int i = 0; i < COMPUTE_TIMES; i++) {
                int j = i * 10000;
            }
            long endTime = System.currentTimeMillis();
            System.out.println("多线程执行结束,当前时间为:" + endTime);
            long used = endTime - startTime;
            System.out.println("多线程执行结束,使用时间为:" + used);
            return used;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        CreateThread createThread = new CreateThread();
        FutureTask<Long> futureTask = new FutureTask<>(createThread);
        Thread thread = new Thread(futureTask, "returnThread");
        thread.start();
        System.out.println("获取多线程执行结果");
        try {
            System.out.println(thread.getName() + "线程占用时间:" + futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("多线程执行结束");
    }
}

4. 通过线程池创建多线程

       前面几种方法创建的Thread实例在执行完成之后是不可复用的,实际工作中需要对已创建好的线程实例进行复用,需要用到线程池。
       ExecutorService是Java提供的一个线程池接口,每次在异步执行target目标任务的时候,可以通过ExecutorService线程池实例去提交或者执行。ExecutorService实例负责对池中的线程进行管理和调度,并且可以有效控制最大并发线程数,提高系统资源的使用率,同时提供定时执行、定频执行、单线程、并发数控制等功能

代码实现如下:

public class MultiThread6 {

    private static final int MAX_TURN = 5;
    private static final int COMPUTE_TIMES = 100000000;
    private static ExecutorService pool = Executors.newFixedThreadPool(3);

    static class RunnableTask implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < MAX_TURN; i++) {
                System.out.println("多线程执行,第:" + i + "次执行");
            }
        }
    }

    static class CallableTask implements Callable<Long> {
        @Override
        public Long call() throws Exception {
            long startTime = System.currentTimeMillis();
            System.out.println("多线程执行,开始时间为:" + startTime);
            Thread.sleep(1000);
            for (int i = 0; i < COMPUTE_TIMES; i++) {
                int j = i * 10000;
            }
            long endTime = System.currentTimeMillis();
            long used = startTime - endTime;
            System.out.println("多线程执行结束,用时:" + used);
            return used;
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        pool.execute(new RunnableTask());
        pool.execute(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < MAX_TURN; i++) {
                    System.out.println("多线程执行,直接实现Runnable");
                }
            }
        });
        Future<Long> future = pool.submit(new CallableTask());
        Long result = future.get();
        System.out.println("异步执行多线程结果为:" + result);
    }
}

注意:实际开发中不会使用Executors创建线程池,而是使用ThreadPoolExecutor的构造方法

4.1. execute()与submit()区别

接收的参数不一样
       submit()可以接收两种入参:无返回值的Runnable类型的target执行目标实例和有返回值的Callable类型的target执行目标实例,execute()只接收无返回值的target执行目标实例或者无返回值的Thread实例
submit()有返回值,execute()没有返回值
       submit()方法在提交异步target执行目标之后会返回Future异步任务实例,以便对target的异步执行过程进行控制,比如取消执行、获取结果等。execute()没有任何返回,target执行目标实例在执行之后没有办法对其异步执行过程进行控制,只能任其执行,直到其执行结束