by emanjusaka from https://www.emanjusaka.top/archives/7 彼岸花开可奈何
本文欢送分享与聚合,全文转载请留下原文地址。

前言

在并发编程中咱们为啥个别选用创立多个线程去解决工作而不是创立多个过程呢?这是因为线程之间切换的开销小,实用于一些要求同时进行并且又要共享某些变量的并发操作。而过程则具备独立的虚拟地址空间,每个过程都有本人独立的代码和数据空间,程序之间的切换会有较大的开销。上面介绍几种创立线程的办法,在这之前咱们还是要先理解一下什么是过程什么是线程。

一、什么是过程和线程

线程是过程中的一个实体,它自身是不会独立存在的。过程是零碎进行资源分配和调度的根本单位,线程则是过程的一个执行门路,一个过程中至多有一个线程,过程中的多个线程共享过程的资源。

过程和线程的关系图如下:

从下面的图中,咱们能够晓得一个过程中有多个线程,多个线程共享过程的堆和办法区资源,然而每个线程都有本人的程序计数器和栈区域。堆是一个过程中最大的一块内存,堆是被过程中的所有线程共享的,是过程创立时调配的,堆外面次要寄存应用new操作创立的对象实例。办法区则用来寄存 JVM 加载的类、常量及动态变量等信息,也是线程共享的。

二、线程的创立

Java 中有几种线程创立的形式:

  • 实现 Runnable 接口的 run 办法
  • 继承 Thread 类并重写 run 的办法

<!---->

  • 应用 FutureTask 形式
  • 应用线程池创立

2.1、实现 Runnable 接口的 run 办法

public static void main(String[] args) {        RunableTask task = new RunableTask();        new Thread(task).start();        new Thread(task).start();    }    public static class RunableTask implements Runnable {        @Override        public void run() {            System.out.println("I am a child thread");        }    }// 输入I am a child threadI am a child thread

这段代码创立了一个RunableTask类,该类实现了Runnable接口,并重写了run()办法。在run()办法中,它打印了一条音讯:"I am a child thread"。

接下来是main()办法,它是Java程序的入口点。在main()办法中,首先创立了一个RunableTask对象,而后通过调用Thread类的构造函数将该对象作为参数传递给Thread类的构造函数,创立了两个新的线程对象。这两个线程对象别离应用start()办法启动,从而使得每个线程都可能并发地执行。

当程序运行时,会创立两个子线程,它们将并发地执行RunableTask对象的run()办法。因为两个线程是同时运行的,因而它们可能会交替执行run()办法中的代码。在这种状况下,因为线程调度的不确定性,可能会呈现以下状况之一:

  • 第一个线程先执行run()办法,打印出"I am a child thread"。
  • 第二个线程先执行run()办法,打印出"I am a child thread"。

须要留神的是,因为线程的执行程序是不确定的,所以每次运行程序时,输入的后果可能会有所不同。

2.2、继承 Thread 类形式的实现

public static void main(String[] args) {        MyThread thread = new MyThread();        thread.start();    }    //继承Thread类并重写run办法    public static class MyThread extends Thread {        @Override        public void run() {            System.out.println("I am a child thread");        }    }

创立一个名为MyThread的类,该类继承了Thread类,并重写了run()办法。

2.3、用 FutureTask 的形式

 public static void main(String[] args) throws InterruptedException {        // 创立异步工作        FutureTask<String> futureTask = new FutureTask<>(new CallerTask());        //启动线程        new Thread(futureTask).start();        try {            //期待工作执行结束,并返回后果            String result = futureTask.get();            System.out.println(result);        } catch (ExecutionException e) {            e.printStackTrace();        }    }    //创立工作类,相似Runable    public static class CallerTask implements Callable<String> {        @Override        public String call() throws Exception {            return "hello emanjusaka";        }    }

下面应用了FutureTask和Callable接口来实现异步工作的执行。首先,在main()办法中创立了一个FutureTask对象,并将一个匿名外部类CallerTask的实例作为参数传递给它。这个匿名外部类实现了Callable接口,并重写了call()办法。在call()办法中,它返回了一个字符串"hello emanjusaka"。接下来,通过调用FutureTask对象的start()办法启动了一个新的线程,该线程会执行CallerTask对象的call()办法。因为start()办法是异步执行的,主线程会继续执行后续的代码。而后,应用futureTask.get()办法来期待异步工作的执行后果。这个办法会阻塞以后线程,直到异步工作执行结束并返回后果。如果工作执行过程中产生了异样,能够通过捕捉ExecutionException来解决异常情况。

须要留神的是,因为异步工作的执行是并发进行的,因而输入的后果可能会有所不同。另外,因为FutureTask和Callable接口提供了更灵便和弱小的性能,因而在须要解决返回后果或解决异样的状况下,它们比继承Thread类并重写run()办法的形式更加不便和牢靠。

2.4、应用线程池

  • Executors

    package top.emanjusaka;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class Main {    public static void main(String[] args) {        // 创立一个固定大小的线程池,大小为5        ExecutorService executor = Executors.newFixedThreadPool(5);        // 提交10个工作到线程池中执行        for (int i = 0; i < 10; i++) {            Runnable worker = new WorkerThread("" + i);            executor.execute(worker);        }        // 敞开线程池        executor.shutdown();        while (!executor.isTerminated()) {        }        System.out.println("所有工作已实现");    }}class WorkerThread implements Runnable {    private String command;    public WorkerThread(String s) {        this.command = s;    }    @Override    public void run() {        System.out.println(Thread.currentThread().getName() + " 开始解决工作: " + command);        processCommand();        System.out.println(Thread.currentThread().getName() + " 实现工作: " + command);    }    private void processCommand() {        try {            Thread.sleep(5000);        } catch (InterruptedException e) {            e.printStackTrace();        }    }}// 输入pool-1-thread-1 开始解决工作: 0pool-1-thread-2 开始解决工作: 1pool-1-thread-3 开始解决工作: 2pool-1-thread-4 开始解决工作: 3pool-1-thread-5 开始解决工作: 4pool-1-thread-2 实现工作: 1pool-1-thread-4 实现工作: 3pool-1-thread-2 开始解决工作: 5pool-1-thread-4 开始解决工作: 6pool-1-thread-1 实现工作: 0pool-1-thread-3 实现工作: 2pool-1-thread-5 实现工作: 4pool-1-thread-3 开始解决工作: 8pool-1-thread-1 开始解决工作: 7pool-1-thread-5 开始解决工作: 9pool-1-thread-2 实现工作: 5pool-1-thread-4 实现工作: 6pool-1-thread-1 实现工作: 7pool-1-thread-3 实现工作: 8pool-1-thread-5 实现工作: 9所有工作已实现

下面的例子中咱们首先创立了一个大小为5的线程池。而后,咱们提交了10个工作到线程池中执行。每个工作都是一个实现了Runnable接口的WorkerThread对象。最初,咱们敞开线程池并期待所有工作实现。

阿里巴巴开发标准倡议应用ThreadPoolExecutor来创立线程池,而不是间接应用Executors。这样做的起因是,Executors创立的线程池可能会存在资源耗尽的危险,而ThreadPoolExecutor则能够更好地控制线程池的运行规定,躲避资源耗尽的危险 。

  • ThreadPoolExecutor

    package top.emanjusaka;import java.util.concurrent.LinkedBlockingQueue;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;public class Main {    public static void main(String[] args) {        // 创立一个固定大小的线程池,大小为5        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());        for (int i = 0; i < 10; i++) {            Runnable worker = new WorkerThread("" + i);            executor.execute(worker);        }        // 敞开线程池        executor.shutdown();        try {            executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("所有工作已实现");    }}class WorkerThread implements Runnable {    private String command;    public WorkerThread(String s) {        this.command = s;    }    @Override    public void run() {        System.out.println(Thread.currentThread().getName() + " 开始解决工作:" + command);        processCommand();        System.out.println(Thread.currentThread().getName() + " 实现工作:" + command);    }    private void processCommand() {        try {            Thread.sleep(5000);        } catch (InterruptedException e) {            e.printStackTrace();        }    }}// 输入pool-1-thread-1 开始解决工作:0pool-1-thread-3 开始解决工作:2pool-1-thread-2 开始解决工作:1pool-1-thread-4 开始解决工作:3pool-1-thread-5 开始解决工作:4pool-1-thread-2 实现工作:1pool-1-thread-3 实现工作:2pool-1-thread-5 实现工作:4pool-1-thread-3 开始解决工作:5pool-1-thread-4 实现工作:3pool-1-thread-1 实现工作:0pool-1-thread-4 开始解决工作:8pool-1-thread-2 开始解决工作:7pool-1-thread-5 开始解决工作:6pool-1-thread-1 开始解决工作:9pool-1-thread-4 实现工作:8pool-1-thread-3 实现工作:5pool-1-thread-2 实现工作:7pool-1-thread-1 实现工作:9pool-1-thread-5 实现工作:6所有工作已实现

在这个例子中,咱们首先创立了一个大小为5的线程池,其中外围线程数为5,最大线程数为10,闲暇线程存活工夫为200毫秒,工作队列为LinkedBlockingQueue。而后,咱们提交了10个工作到线程池中执行。最初,咱们敞开线程池并期待所有工作实现。

ThreadPoolExecutor的构造函数有以下参数:

  1. corePoolSize:外围线程数,即线程池中始终保持沉闷的线程数。
  2. maximumPoolSize:最大线程数,即线程池中容许的最大线程数。当工作队列满了之后,线程池会创立新的线程来解决工作,直到达到最大线程数。
  3. keepAliveTime:闲暇线程存活工夫,即当线程池中的线程数量超过外围线程数时,多余的闲暇线程在期待新工作的最长工夫。超过这个工夫后,闲暇线程将被销毁。
  4. unit:keepAliveTime的工夫单位,例如TimeUnit.SECONDS示意秒,TimeUnit.MILLISECONDS示意毫秒。
  5. workQueue:工作队列,用于寄存待处理的工作。罕用的有ArrayBlockingQueue、LinkedBlockingQueue和SynchronousQueue等。
  6. threadFactory:线程工厂,用于创立新的线程。能够自定义线程的名称、优先级等属性。
  7. handler:回绝策略,当工作队列满了且线程池已满时,线程池如何解决新提交的工作。罕用的有AbortPolicy(抛出异样)、DiscardPolicy(抛弃工作)和DiscardOldestPolicy(抛弃队列中最旧的工作)。
  8. executorListeners:监听器,用于监听线程池的状态变动。罕用的有ThreadPoolExecutor.AbortPolicy、ThreadPoolExecutor.CallerRunsPolicy和ThreadPoolExecutor.DiscardPolicy。

三、总结

应用继承形式的益处是不便传参,你能够在子类外面增加成员变量,通过set办法设置参数或者通过构造函数进行传递,而如果应用Runnable形式,则只能应用主线程外面被申明为final的变量。不好的中央是Java不反对多继承,如果继承了Thread类,那么子类不能再继承其余类,而Runable则没有这个限度。前两种形式都没方法拿到工作的返回后果,然而Futuretask形式能够。

应用Callable和Future创立线程。这种形式能够将线程作为工作提交给线程池执行,而且能够获取到线程的执行后果。然而须要留神的是,如果线程抛出了异样,那么在主线程中是无奈获取到的。应用线程池。线程池是一种治理线程的机制,能够无效地控制线程的数量和复用线程,防止了频繁地创立和销毁线程带来的性能开销。

参考资料

  1. 《Java并发编程之美》

本文原创,满腹经纶,如有纰漏,欢送斧正。尊贵的敌人,如果本文对您有所帮忙,欢送点赞,并期待您的反馈,以便于一直优化。

原文地址: https://www.emanjusaka.top/archives/7

微信公众号:emanjusaka的编程栈