Java 实现多线程的四种方式

  1. 继承 Thread 类
  2. 实现 Runnable 接口
  3. 实现 Callable 接口
  4. 线程池

下面我将对这四种方式进行入门级的解析和演示。

一、继承 Thread 类

通过继承 Thread 类实现多线程的步骤如下:

创建 MyThread 类,让其继承 Thread 类并重写 run() 方法。
创建 MyThread 类的实例对象,即创建一个新线程。
调用 start() 方法,启动线程。

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("我是通过继承 Thread 类创建的多线程,我叫" + Thread.currentThread().getName());
    }
}

class TestMyThread {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        myThread1.setName("Thread-1");
        MyThread myThread2 = new MyThread();
        myThread2.setName("Thread-2");
        MyThread myThread3 = new MyThread();
        myThread3.setName("Thread-3");

        myThread1.start();
        myThread2.start();
        myThread3.start();
    }
}

为了演示线程执行顺序的随机性,我特意创建了三个线程,并为每一个线程命名,下面是我运行3次程序的执行结果:

// 第一次
我是通过继承 Thread 类创建的多线程,我叫Thread-2
我是通过继承 Thread 类创建的多线程,我叫Thread-1
我是通过继承 Thread 类创建的多线程,我叫Thread-3

// 第二次
我是通过继承 Thread 类创建的多线程,我叫Thread-1
我是通过继承 Thread 类创建的多线程,我叫Thread-3
我是通过继承 Thread 类创建的多线程,我叫Thread-2

// 第三次
我是通过继承 Thread 类创建的多线程,我叫Thread-1
我是通过继承 Thread 类创建的多线程,我叫Thread-3
我是通过继承 Thread 类创建的多线程,我叫Thread-2

从上面的执行结果我们可以看到线程的执行顺序和代码中编写的顺序没有关系,线程的执行顺序是具有随机性的。

二、实现 Runnable 接口

Runnable 接口只有一个 run() 方法,源码如下:

public interface Runnable {
    public abstract void run();
}

通过实现 Runnable 接口实现多线程的步骤如下:

创建 MyRunnable 类实现 Runnable 接口。
创建 MyRunnable 类的实例对象 myRunnable 。
把实例对象 myRunnable 作为参数来创建 Thread 类的实例对象 thread,实例对象 thread 就是一个新线程。
调用 start() 方法,启动线程。
代码示例如下:

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("我是通过实现 Runnable 接口创建的多线程,我叫" + Thread.currentThread().getName());
    }
}

class TestMyRunnable {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
    }
}

执行结果如下:

我是通过实现 Runnable 接口创建的多线程,我叫Thread-0

相比于继承 Thread 类的方法来说,实现 Runnable 接口是一个更好地选择,因为 Java 不支持多继承,但是可以实现多个接口。

有一点值得注意的是 Thread 类也实现了 Runnable 接口,这意味着构造函数 Thread(Runnable target) 不仅可以传入 Runnable 接口的对象,而且可以传入一个 Thread 类的对象,这样就可以将一个 Thread 对象中的 run() 方法交由其他线程进行调用。

三、实现 Callable 接口

Callable 接口只有一个 call() 方法,源码如下:

public interface Callable<V> {
    V call() throws Exception;
}

从源码我们可以看到 Callable 接口和 Runnable 接口类似,它们之间的区别在于 run() 方法没有返回值,而 call() 方法是有返回值的。

通过实现 Callable 接口实现多线程的步骤如下:

创建 MyCallable 类实现 Callable 接口。
创建 MyCallable 类的实例对象 myCallable。
把实例对象 myCallable 作为参数来创建 FutureTask 类的实例对象 futureTask。
把实例对象 futureTask 作为参数来创建 Thread 类的实例对象 thread,实例对象 thread 就是一个新线程。
调用 start() 方法,启动线程。
代码示例如下:

public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int a = 6;
        int b = 9;
        System.out.println("我是通过实现 Callable 接口创建的多线程,我叫" + Thread.currentThread().getName());
        return a + b;
    }
}

class TestMyCallable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable myCallable = new MyCallable();
        FutureTask<Integer> futureTask = new FutureTask(myCallable);
        Thread thread = new Thread(futureTask);
        thread.start();
        System.out.println("返回值为:" + futureTask.get());
    }
}

执行结果如下:

我是通过实现 Callable 接口创建的多线程,我叫Thread-0
返回值为:15

FutureTask 类提供了一个 get() 方法用来获取 call() 方法的返回值,但需要注意的是调用这个方法会导致程序阻塞,必须要等到线程结束后才会得到返回值。

四、线程池

Java通过Executors创建线程池,分别为:

1,Executors.newCachedThreadPool()

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程, 适用于服务器负载较轻,执行很多短期异步任务

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

2,Executors.newFixedThreadPool(3)

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待,适用于可以预测线程数量的业务中,或者服务器负载较重,对当前线程数量进行限制。

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

3,Executors.newScheduledThreadPool(3)

创建一个定长线程池,支持定时及周期性任务执行,适用于需要多个后台线程执行周期任务的场景。

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

4,newSingleThreadExecutor()

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行,适用于需要保证顺序执行各个任务,并且在任意时间点,不会有多个线程时活动的场景。

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

5.Executors.newWorkStealingPool()

创建一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行,适用于大耗时的操作,可以并行来执行

 public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

线程池核心的参数

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }

一、corePoolSize 线程池核心线程大小
二、maximumPoolSize 线程池最大线程数量
三、keepAliveTime 空闲线程存活时间
四、unit 空闲线程存活时间单位
五、workQueue 工作队列
六、threadFactory 线程工厂
七、handler 拒绝策略

线程池四种拒绝任务策略

1、直接丢弃(DiscardPolicy)

2、丢弃队列中最早的任务(DiscardOldestPolicy)。

3、抛异常(AbortPolicy)

4、将任务分给调用线程来执行(CallerRunsPolicy)。

工作队列

①ArrayBlockingQueue
基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。
②LinkedBlockingQuene
基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而基本不会去创建新线程直到maxPoolSize(很难达到Interger.MAX这个数),因此使用该工作队列时,参数maxPoolSize其实是不起作用的。
③SynchronousQuene
一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。
④PriorityBlockingQueue
具有优先级的无界阻塞队列,优先级通过参数Comparator实现。

public PriorityBlockingQueue(int initialCapacity,
                                 Comparator<? super E> comparator) {
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.lock = new ReentrantLock();
        this.notEmpty = lock.newCondition();
        this.comparator = comparator;
        this.queue = new Object[initialCapacity];
    }

测试ThreadPoolExecutor对线程的执行顺序及拒绝策略

package com.it.test;

import java.time.LocalTime;
import java.util.concurrent.*;

/**
 * 测试ThreadPoolExecutor对线程的执行顺序 
 **/
public class ThreadPoolTest {
    public static void main(String[] args) {
        //核心线程数
        int corePoolSize = 3;
        //最大线程数
        int maximumPoolSize = 6;
        //超过 corePoolSize 线程数量的线程最大空闲时间
        long keepAliveTime = 2;
        //以秒为时间单位
        TimeUnit unit = TimeUnit.SECONDS;
        //创建工作队列,用于存放提交的等待执行任务
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(3);
        ThreadPoolExecutor threadPoolExecutor = null;
        //ExecutorService pool = Executors.newFixedThreadPool(5);
        try {
            //创建线程池
            threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,
                    maximumPoolSize,
                    keepAliveTime,
                    unit,
                    workQueue,
                    new ThreadPoolExecutor.CallerRunsPolicy());
 
            //循环提交任务
            for (int i = 0; i < 10; i++) {
                //提交任务的索引
                final int index = (i + 1);
                threadPoolExecutor.submit(() -> {
                    //线程打印输出
                    System.out.println("【" + LocalTime.now() + "】线程 " + Thread.currentThread() + "正在执行任务" + index);
                    try {
                        //模拟线程执行时间
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
                //每个任务提交后休眠500ms再提交下一个任务,用于保证提交顺序
                Thread.sleep(500);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            threadPoolExecutor.shutdown();
        }
    }
}

测试不同的拒绝策略:修改new ThreadPoolExecutor.CallerRunsPolicy();

CallerRunsPolicy10:31:33.086】线程 Thread[pool-1-thread-1,5,main]正在执行任务110:31:33.545】线程 Thread[pool-1-thread-2,5,main]正在执行任务210:31:34.056】线程 Thread[pool-1-thread-3,5,main]正在执行任务310:31:36.103】线程 Thread[pool-1-thread-4,5,main]正在执行任务710:31:36.609】线程 Thread[pool-1-thread-5,5,main]正在执行任务810:31:37.116】线程 Thread[pool-1-thread-6,5,main]正在执行任务910:31:37.625】线程 Thread[main,5,main]正在执行任务1010:31:38.090】线程 Thread[pool-1-thread-1,5,main]正在执行任务410:31:38.557】线程 Thread[pool-1-thread-2,5,main]正在执行任务510:31:39.070】线程 Thread[pool-1-thread-3,5,main]正在执行任务6
DisCardPolicy10:34:03.585】线程 Thread[pool-1-thread-1,5,main]正在执行任务110:34:04.014】线程 Thread[pool-1-thread-2,5,main]正在执行任务210:34:04.526】线程 Thread[pool-1-thread-3,5,main]正在执行任务310:34:06.573】线程 Thread[pool-1-thread-4,5,main]正在执行任务710:34:07.082】线程 Thread[pool-1-thread-5,5,main]正在执行任务810:34:07.583】线程 Thread[pool-1-thread-6,5,main]正在执行任务910:34:08.594】线程 Thread[pool-1-thread-1,5,main]正在执行任务410:34:09.015】线程 Thread[pool-1-thread-2,5,main]正在执行任务510:34:09.529】线程 Thread[pool-1-thread-3,5,main]正在执行任务6
DiscardOldestPolicy10:35:34.600】线程 Thread[pool-1-thread-1,5,main]正在执行任务110:35:35.034】线程 Thread[pool-1-thread-2,5,main]正在执行任务210:35:35.544】线程 Thread[pool-1-thread-3,5,main]正在执行任务310:35:37.581】线程 Thread[pool-1-thread-4,5,main]正在执行任务710:35:38.091】线程 Thread[pool-1-thread-5,5,main]正在执行任务810:35:38.600】线程 Thread[pool-1-thread-6,5,main]正在执行任务910:35:39.617】线程 Thread[pool-1-thread-1,5,main]正在执行任务510:35:40.035】线程 Thread[pool-1-thread-2,5,main]正在执行任务610:35:40.546】线程 Thread[pool-1-thread-3,5,main]正在执行任务10
AbortPolicy10:36:38.723】线程 Thread[pool-1-thread-1,5,main]正在执行任务110:36:39.164】线程 Thread[pool-1-thread-2,5,main]正在执行任务210:36:39.675】线程 Thread[pool-1-thread-3,5,main]正在执行任务310:36:41.720】线程 Thread[pool-1-thread-4,5,main]正在执行任务710:36:42.232】线程 Thread[pool-1-thread-5,5,main]正在执行任务810:36:42.744】线程 Thread[pool-1-thread-6,5,main]正在执行任务9
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@77556fd rejected from java.util.concurrent.ThreadPoolExecutor@368239c8[Running, pool size = 6, active threads = 6, queued tasks = 3, completed tasks = 0]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
	at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
	at com.formssi.mall.pay.application.ThreadPoolTest.main(ThreadPoolTest.java:44)10:36:43.725】线程 Thread[pool-1-thread-1,5,main]正在执行任务410:36:44.178】线程 Thread[pool-1-thread-2,5,main]正在执行任务510:36:44.678】线程 Thread[pool-1-thread-3,5,main]正在执行任务6

Process finished with exit code 1
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐