共计 6920 个字符,预计需要花费 18 分钟才能阅读完成。
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 thread
I 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 开始解决工作:0 pool-1-thread-2 开始解决工作:1 pool-1-thread-3 开始解决工作:2 pool-1-thread-4 开始解决工作:3 pool-1-thread-5 开始解决工作:4 pool-1-thread-2 实现工作:1 pool-1-thread-4 实现工作:3 pool-1-thread-2 开始解决工作:5 pool-1-thread-4 开始解决工作:6 pool-1-thread-1 实现工作:0 pool-1-thread-3 实现工作:2 pool-1-thread-5 实现工作:4 pool-1-thread-3 开始解决工作:8 pool-1-thread-1 开始解决工作:7 pool-1-thread-5 开始解决工作:9 pool-1-thread-2 实现工作:5 pool-1-thread-4 实现工作:6 pool-1-thread-1 实现工作:7 pool-1-thread-3 实现工作:8 pool-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 开始解决工作:0 pool-1-thread-3 开始解决工作:2 pool-1-thread-2 开始解决工作:1 pool-1-thread-4 开始解决工作:3 pool-1-thread-5 开始解决工作:4 pool-1-thread-2 实现工作:1 pool-1-thread-3 实现工作:2 pool-1-thread-5 实现工作:4 pool-1-thread-3 开始解决工作:5 pool-1-thread-4 实现工作:3 pool-1-thread-1 实现工作:0 pool-1-thread-4 开始解决工作:8 pool-1-thread-2 开始解决工作:7 pool-1-thread-5 开始解决工作:6 pool-1-thread-1 开始解决工作:9 pool-1-thread-4 实现工作:8 pool-1-thread-3 实现工作:5 pool-1-thread-2 实现工作:7 pool-1-thread-1 实现工作:9 pool-1-thread-5 实现工作:6 所有工作已实现
在这个例子中,咱们首先创立了一个大小为 5 的线程池,其中外围线程数为 5,最大线程数为 10,闲暇线程存活工夫为 200 毫秒,工作队列为 LinkedBlockingQueue。而后,咱们提交了 10 个工作到线程池中执行。最初,咱们敞开线程池并期待所有工作实现。
ThreadPoolExecutor 的构造函数有以下参数:
- corePoolSize:外围线程数,即线程池中始终保持沉闷的线程数。
- maximumPoolSize:最大线程数,即线程池中容许的最大线程数。当工作队列满了之后,线程池会创立新的线程来解决工作,直到达到最大线程数。
- keepAliveTime:闲暇线程存活工夫,即当线程池中的线程数量超过外围线程数时,多余的闲暇线程在期待新工作的最长工夫。超过这个工夫后,闲暇线程将被销毁。
- unit:keepAliveTime 的工夫单位,例如 TimeUnit.SECONDS 示意秒,TimeUnit.MILLISECONDS 示意毫秒。
- workQueue:工作队列,用于寄存待处理的工作。罕用的有 ArrayBlockingQueue、LinkedBlockingQueue 和 SynchronousQueue 等。
- threadFactory:线程工厂,用于创立新的线程。能够自定义线程的名称、优先级等属性。
- handler:回绝策略,当工作队列满了且线程池已满时,线程池如何解决新提交的工作。罕用的有 AbortPolicy(抛出异样)、DiscardPolicy(抛弃工作)和 DiscardOldestPolicy(抛弃队列中最旧的工作)。
- executorListeners:监听器,用于监听线程池的状态变动。罕用的有 ThreadPoolExecutor.AbortPolicy、ThreadPoolExecutor.CallerRunsPolicy 和 ThreadPoolExecutor.DiscardPolicy。
三、总结
应用继承形式的益处是不便传参,你能够在子类外面增加成员变量,通过 set 办法设置参数或者通过构造函数进行传递,而如果应用 Runnable 形式,则只能应用主线程外面被申明为 final 的变量。不好的中央是 Java 不反对多继承,如果继承了 Thread 类,那么子类不能再继承其余类,而 Runable 则没有这个限度。前两种形式都没方法拿到工作的返回后果,然而 Futuretask 形式能够。
应用 Callable 和 Future 创立线程。这种形式能够将线程作为工作提交给线程池执行,而且能够获取到线程的执行后果。然而须要留神的是,如果线程抛出了异样,那么在主线程中是无奈获取到的。应用线程池。线程池是一种治理线程的机制,能够无效地控制线程的数量和复用线程,防止了频繁地创立和销毁线程带来的性能开销。
参考资料
- 《Java 并发编程之美》
本文原创,满腹经纶,如有纰漏,欢送斧正。尊贵的敌人,如果本文对您有所帮忙,欢送点赞,并期待您的反馈,以便于一直优化。
原文地址:https://www.emanjusaka.top/archives/7
微信公众号:emanjusaka 的编程栈