1. 实现多线程有几种方式?有什么区别?
实现多线程有 3 种方式。
1.1 继承 Thread 类
继承 Thread 类,重新 run() 方法。实现代码如下:
 public class ExtendsThread extends Thread{    @Override    public void run() {        System.out.println("run ExtendsThread");    }}
使用线程:
public class LearningThread {    public static void main(String[] args) {        func1();    }    public static void func1(){        ExtendsThread extendsThread = new ExtendsThread();        System.out.println("run func1");        extendsThread.start();        System.out.println("run func1 end");    }}
       复制代码
 1.2 实现 Runnable 接口
实现 Runnable 接口,实现 run() 方法,通过 Thread 类来开启线程。代码如下:
 public class ImplRunnable implements Runnable {    @Override    public void run() {        System.out.println("run ImplRunnable");    }}
使用线程:
public class LearningThread {    public static void main(String[] args) {        func2();    }
    public static void func2(){        ImplRunnable implRunnable = new ImplRunnable();        System.out.println("run func2");        Thread thread = new Thread(implRunnable);        thread.start();        System.out.println("run func2 end");    }}
       复制代码
 1.3 实现 Callable 接口
实现 Callable 接口,实现 call() 方法。代码如下:
 public class ImplCallable implements Callable<Integer> {    @Override    public Integer call() throws Exception {        return 2020;    }}使用线程:
public class LearningThread {    public static void main(String[] args) {        func3();    }
    public static void func3() throws ExecutionException, InterruptedException {        Callable<Integer> integerCallable = new ImplCallable();        System.out.println("run func3");        FutureTask<Integer> futureTask = new FutureTask<>(integerCallable);        Thread thread = new Thread(futureTask);        thread.start();        System.out.println("run func3 end");        System.out.println("futureTask.get = " + futureTask.get());    }}
       复制代码
 
这三种方式都可以实现多线程,第一种由于 Java 的单继承,不建议使用。至于实现 Runnable 接口,与实现 Callable 接口,的区别是,实现 Callable 接口后通过 futureTask.get() 方法可以获取线程内的执行结果。而 Runnable 是没有返回值的。
2. 为什么要用线程池?
线程池主要是为了减少每次创建线程时的资源消耗,重复利用创建好的线程,提高资源利用率。使用线程池由于减少了线程创建的过程,在每次接到请求时可以及时响应,提高响应速度。通过线程池统一管理线程,方便线程的分配,调优和监控。
3. 创建线程池的时候有哪些参数?
在 Java 源码中,创建线程池的构造方法最多有 7 个参数。
 public ThreadPoolExecutor(int corePoolSize,                          int maximumPoolSize,                          long keepAliveTime,                          TimeUnit unit,                          BlockingQueue<Runnable> workQueue,                          ThreadFactory threadFactory,                          RejectedExecutionHandler handler)
       复制代码
 
corePoolSize: 核心线程数,线程池的工作线程数量。
maximumPoolSize: 最大线程数,线程池中可以存活的最多的线程数量。当队列满了之后,会启用非核心线程,此时的线程池的大小变为最大线程数。
keepAliveTime: 非核心线程如果没有任务的话,可以存活的时间。
unit: 非核心线程存活时间的单位。
workQueue: 工作队列。当线程池中的线程达到 corePoolSize ,如果再来任务,就会放到工作队列里。
threadFactory: 线程工厂,线程池创建线程时使用的工厂。
handler:拒绝策略。如果线程池的线程达到了 maximumPoolSize ,如果再来任务,则执行拒绝策略。
4. 线程池是如何工作的?
当有任务提交到线程池时,首先启动核心线程。随着任务的增加,当核心线程用完之后,再次提交的线程将会进入工作队列。当工作队列满了之后,如果再次提交到线程池任务,将会判断,核心线程数是否小于最大线程数,如果小于,将会启用非核心线程,当工作的线程达到最大线程数后,如果还继续提交任务到线程池,则会执行拒绝策略来拒绝任务。
5. 常见的拒绝策略有哪些?
JDK 自带了 4 种拒绝策略。
5.1 AbortPolicy
直接丢弃任务,抛出 RejectedExecutionException
 public class ThreadPoolRunnable implements Runnable {    private int number = 0;
    public ThreadPoolRunnable(int number) {        this.number = number;    }
    @Override    public void run() {        System.out.println("run "+Thread.currentThread().getName()+", number = " + number);        try {            Thread.sleep(3000);        } catch (InterruptedException e) {            e.printStackTrace();        }    }}
使用线程池:
    public static void func4(){        ThreadPoolExecutor executor = new ThreadPoolExecutor(2,3,10,                TimeUnit.SECONDS, new ArrayBlockingQueue<>(1), new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0; i < 7; i++) {            Runnable runnable = new ThreadPoolRunnable(i);            executor.execute(runnable);        }        executor.shutdown();    }
执行结果:
run pool-1-thread-1, number = 0run pool-1-thread-3, number = 3run pool-1-thread-2, number = 1Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.learning.thread.ThreadPoolRunnable@1d44bcfa rejected from java.util.concurrent.ThreadPoolExecutor@266474c2[Running, pool size = 3, active threads = 3, queued tasks = 1, 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 com.learning.thread.LearningThread.func4(LearningThread.java:45)    at com.learning.thread.LearningThread.main(LearningThread.java:11)run pool-1-thread-1, number = 2
       复制代码
 
由于最大线程数是 3 ,队列大小是 1,所以线程池最多可以同时存在 4 个线程,当提交第 5 任务时,主线程抛出异常。
5.2 CallerRunsPolicy
调用启用线程池的线程进行处理,不过会阻塞主线程。
     public static void func4(){        ThreadPoolExecutor executor = new ThreadPoolExecutor(2,3,10,                TimeUnit.SECONDS, new ArrayBlockingQueue<>(1), new ThreadPoolExecutor.CallerRunsPolicy());
        for (int i = 0; i < 7; i++) {            Runnable runnable = new ThreadPoolRunnable(i);            executor.execute(runnable);        }        executor.shutdown();    }
执行结果:
run pool-1-thread-1, number = 0run main, number = 4run pool-1-thread-3, number = 3run pool-1-thread-2, number = 1run main, number = 5run pool-1-thread-3, number = 2run pool-1-thread-3, number = 6
       复制代码
 
可以看到 第 5 个任务,即 number = 4,是在主线程中执行的,由于主线程被阻塞,导致第 6 个任务不能立即提交,当主线程执行结束后再提交时,线程池里已经有可用的线程了,所以第 6 个任务是线程池执行的。
5.3 DiscardPolicy
直接拒绝任务,不抛出任何异常
     public static void func4(){        ThreadPoolExecutor executor = new ThreadPoolExecutor(2,3,10,                TimeUnit.SECONDS, new ArrayBlockingQueue<>(1), new ThreadPoolExecutor.DiscardPolicy());
        for (int i = 0; i < 7; i++) {            Runnable runnable = new ThreadPoolRunnable(i);            executor.execute(runnable);        }        executor.shutdown();    }
执行结果:
run pool-1-thread-2, number = 1run pool-1-thread-3, number = 3run pool-1-thread-1, number = 0run pool-1-thread-2, number = 2
       复制代码
 5.4 DiscardOldestPolicy
抛弃队列中最先加入的任务,然后将当前任务提交到线程池。
     public static void func4(){        ThreadPoolExecutor executor = new ThreadPoolExecutor(2,3,10,                TimeUnit.SECONDS, new ArrayBlockingQueue<>(1), new ThreadPoolExecutor.DiscardOldestPolicy());
        for (int i = 0; i < 7; i++) {            Runnable runnable = new ThreadPoolRunnable(i);            executor.execute(runnable);        }        executor.shutdown();    }执行结果:
run pool-1-thread-1, number = 0run pool-1-thread-3, number = 3run pool-1-thread-2, number = 1run pool-1-thread-1, number = 6
       复制代码
 
number 0、1,使用核心线程执行。
number 2,放入队列。
number 3,启用非核心线程执行。
number 4,抛弃 number 2,将 number 4 放入队列
number 5,抛弃 number 4,将 number 5 放入队列
number 6,抛弃 number 5,将 number 6 放入队列
执行 number 6
5.5 自定义拒绝策略
当然,如果上面的拒绝策略都不满足的话,我们也可以定义拒绝策略。
 public class MyRejectedExecutionHandler implements RejectedExecutionHandler {    @Override    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {        System.out.println(executor.toString());    }}
执行结果:
run pool-1-thread-2, number = 1run pool-1-thread-3, number = 3run pool-1-thread-1, number = 0java.util.concurrent.ThreadPoolExecutor@1d44bcfa[Running, pool size = 3, active threads = 3, queued tasks = 1, completed tasks = 0]java.util.concurrent.ThreadPoolExecutor@1d44bcfa[Running, pool size = 3, active threads = 3, queued tasks = 1, completed tasks = 0]java.util.concurrent.ThreadPoolExecutor@1d44bcfa[Running, pool size = 3, active threads = 3, queued tasks = 1, completed tasks = 0]run pool-1-thread-2, number = 2
       复制代码
 6. 常见的线程池有哪些?各自有什么特点?
JDK 自带了几个常见的线程池。
6.1 FixedThreadPool
FixedThreadPool 被称为可重用固定线程数的线程池。创建的源码如下:
     public static ExecutorService newFixedThreadPool(int nThreads) {        return new ThreadPoolExecutor(nThreads, nThreads,                                      0L, TimeUnit.MILLISECONDS,                                      new LinkedBlockingQueue<Runnable>());    }
       复制代码
 
可以看到 FixedThreadPool 的的核心线程数和最大线程数都是传入的参数。使用的队列是 LinkedBlockingQueue 。由于 LinkedBlockingQueue 是一个无界队列(队列的容量为 Intger.MAX_VALUE),所以运行中的 FixedThreadPool 不会拒绝任务,所以当任务过多的时候可能会造成 OOM 。
6.2 SingleThreadExecutor
SingleThreadExecutor 是只有一个线程的线程池。创建源码:
     public static ExecutorService newSingleThreadExecutor() {        return new FinalizableDelegatedExecutorService            (new ThreadPoolExecutor(1, 1,                                    0L, TimeUnit.MILLISECONDS,                                    new LinkedBlockingQueue<Runnable>()));    }
       复制代码
 
SingleThreadExecutor 的核心线程数和最大线程数都为 1,所以这个线程池只有一个线程。使用的队列也是 LinkedBlockingQueue ,所以当任务过多时也会存在 OOM 的问题。
6.3 CachedThreadPool
CachedThreadPool 无固定大小的线程池,随着任务的不断提交,创建新的线程来执行。创建源码:
     public static ExecutorService newCachedThreadPool() {        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,                                      60L, TimeUnit.SECONDS,                                      new SynchronousQueue<Runnable>());    }
       复制代码
 
CachedThreadPool 的核心线程数为 0 ,最大线程数是 Integer.MAX_VALUE,可见所有的线程都是非核心线程。如果线程池的线程 60 秒,没有执行任务则会被销毁。由于使用了 SynchronousQueue,所以当主线程通过 SynchronousQueue.offer(Runnable task) 提交任务到队列后会阻塞,如果线程池中有可用的线程,则会执行当前任务,如果没有则会创建一个新的线程来执任务。
可见,如果任务太多的话,依然会造成 OOM ,与 LinkedBlockingQueue 不同的是,LinkedBlockingQueue 是由于任务对象太多,导致 OOM,ThreadPoolExecutor 则是由于 线程数太多导致 OOM 。
如何获取线程池中的返回结果?
可以使用 executor.submit(futureTask);,提交一个 FutureTask。代码如下:
 自定义的线程public class ThreadPoolCallable implements Callable<Integer> {    @Override    public Integer call() throws Exception {        Random random = new Random();        int number = random.nextInt(100);        System.out.println(Thread.currentThread().getName() + ":" + number);        return number;    }}
使用:    public static void func5() throws ExecutionException, InterruptedException {        ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 3, 10,                TimeUnit.SECONDS, new ArrayBlockingQueue<>(1), new ThreadPoolExecutor.CallerRunsPolicy());        int result = 0;        for (int i = 0; i < 7; i++) {            Callable<Integer> callable = new ThreadPoolCallable();            FutureTask<Integer> futureTask = new FutureTask<>(callable);            executor.submit(futureTask);            result += futureTask.get();        }        executor.shutdown();
        System.out.println("result = " + result);    }
执行结果:
pool-1-thread-1:41pool-1-thread-2:83pool-1-thread-1:15pool-1-thread-2:21pool-1-thread-1:26pool-1-thread-2:38pool-1-thread-1:98result = 322
       复制代码
 
评论