Java17(线程池、Runnable和Callable、Lock、信号量、任务调度、Timer)

一、线程池概念

    在Java中,如果每个请求到达就会创建一个新线程,开销是相当大的。

  线程池就是来解决生命周期开销问题和资源不足问题。通过多任务重复使用线程,线程创建的开销被分摊到多个任务上,而且由于在请求到达前时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,是应用程序响应更快。

  通过适当的调整线程中的线程数目,可以防止资源不足的情况。

  当服务器接受到大量短小的请求时,使用线程池技术是非常合适的,他可以大大减少线程的创建和销毁次数,提高服务器的工作效率。  

  一个比较简单线程池应该包括:

    线程池管理器:

      创建、销毁并管理线程池,将工作线程放入池中

    工作线程:

      一个可以循环执行任务的线程,在没有任务时进行等待

    任务队列:

      提供一种缓冲机制,将没有处理的任务放在任务队列中

    任务接口:

      每个任务必须实现的接口,主要用来规定任务的入口、任务执行完后的收尾工作、任务的执行状态等,工作线程通过该接口调度任务的执行

   简易线程池代码:

 1 package com.chinasofti.executor;
 2 
 3 public class ExecutorThread extends Thread {
 4     // 创建一个属性表示是否运行项目
 5     boolean isRunning = false;
 6     // 创建一个Runnable接口表示执行的方法
 7     private Runnable run;
 8 
 9     public synchronized void setRunning(boolean running) {
10         isRunning = running;
11         // 如果运行项目运行 那么就唤醒线程
12         if(isRunning){
13            this.notify();
14         }
15     }
16 
17     public boolean isRunning() {
18         return isRunning;
19     }
20 
21     public Runnable getRun() {
22         return run;
23     }
24 
25     public void setRun(Runnable run) {
26         this.run = run;
27     }
28 
29     @Override
30     public synchronized void run() {
31         while (true){
32             // 如果项目运行 就执行规定的方法
33             if(isRunning){
34                 this.run.run();
35                 isRunning = false;
36             // 否则就继续等待
37             }else {
38                 try {
39                     this.wait();
40                 } catch (InterruptedException e) {
41                     e.printStackTrace();
42                 }
43             }
44         }
45     }
46 }
View Code
 1 package com.chinasofti.executor;
 2 
 3 import java.util.ArrayList;
 4 import java.util.Collections;
 5 import java.util.List;
 6 
 7 public class ThreadPoolExecutor {
 8     // 创建线程池容器
 9     private List<ExecutorThread> list;
10     // 创建构造方法 num表示线程池的数量
11     public ThreadPoolExecutor(int num) {
12         // new一个线程池的集合,转换成线程安全集合
13         this.list = Collections.synchronizedList(new ArrayList<ExecutorThread>());
14         // 循环创建num个线程并执行
15         for (int i = 0; i < num; i++) {
16             list.add(new ExecutorThread());
17             list.get(i).start();
18         }
19     }
20 
21     // 创建一个线程池工作的方法
22     public void execute(Runnable run){
23         // 遍历线程池中的线程 判断是否正在运行
24         int i;
25         for ( i = 0;  i < list.size();  i++) {
26             ExecutorThread et = list.get(i);
27             // 如果当前线程没有运行 则添加方法
28             if(!et.isRunning()){
29                 et.setRun(run);
30                 et.setRunning(true);
31                 return;
32             }
33         }
34         // 如果线程池的线程均在运行 则提示
35         if(i==list.size()){
36             System.out.println("线程池中线程用尽");
37         }
38     }
39 
40 
41 }
View Code

 

 1 package com.chinasofti.executor;
 2 
 3 public class Test {
 4     public static void main(String[] args) {
 5         // 先new一个池子
 6         ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5);
 7         // 使用池子中的线程 执行方法
 8         threadPoolExecutor.execute(()->{
 9             for (int i = 0; i < 8; i++) {
10                 try {
11                     Thread.sleep(500);
12                 } catch (InterruptedException e) {
13                     e.printStackTrace();
14                 }
15                 System.out.println(Thread.currentThread().getName() + "---" + i);
16             }
17         });
18 
19         threadPoolExecutor.execute(()->{
20             for (int i = 0; i < 8; i++) {
21                 try {
22                     Thread.sleep(500);
23                 } catch (InterruptedException e) {
24                     e.printStackTrace();
25                 }
26                 System.out.println(Thread.currentThread().getName() + "---" + i);
27             }
28         });
29 
30         threadPoolExecutor.execute(()->{
31             for (int i = 0; i < 8; i++) {
32                 try {
33                     Thread.sleep(500);
34                 } catch (InterruptedException e) {
35                     e.printStackTrace();
36                 }
37                 System.out.println(Thread.currentThread().getName() + "---" + i);
38             }
39         });
40 
41         threadPoolExecutor.execute(()->{
42             for (int i = 0; i < 8; i++) {
43                 try {
44                     Thread.sleep(500);
45                 } catch (InterruptedException e) {
46                     e.printStackTrace();
47                 }
48                 System.out.println(Thread.currentThread().getName() + "---" + i);
49             }
50         });
51 
52         threadPoolExecutor.execute(()->{
53             for (int i = 0; i < 8; i++) {
54                 try {
55                     Thread.sleep(500);
56                 } catch (InterruptedException e) {
57                     e.printStackTrace();
58                 }
59                 System.out.println(Thread.currentThread().getName() + "---" + i);
60             }
61         });
62 
63         threadPoolExecutor.execute(()->{
64             for (int i = 0; i < 8; i++) {
65                 try {
66                     Thread.sleep(500);
67                 } catch (InterruptedException e) {
68                     e.printStackTrace();
69                 }
70                 System.out.println(Thread.currentThread().getName() + "---" + i);
71             }
72         });
73 
74 
75     }
76 }
View Code

 

  Executor框架:

    引入Executor的框架的最大优点就是把任务和执行解耦,要执行任务只需要把task描述清楚,然后提交即可。至于这个task是如何执行,被谁执行,什么时候执行,提交的人就不必要担心了。具体点说,提交一个Callable对象,给ExecutorService(如最常用的线程池ThreadPoolExecutor),将得到一个future对象,调用future对象的get方法等待执行结果就好了。

    

 

 

       

 

  Future对象的api

    

 

 

   Executor是一个可以提交可执行任务的工具,这个接口解耦了任务提交和执行的细节。Executor主要来替代显示的创建和运行线程。

  ExecutorService提供了异步的管理一个或多个线程终止、执行过程的方法。

  Executors类提供了一系列工厂方法用于创建任务执行器,返回的任务执行器都都实现了ExecutorService接口。

           

  有几种不同的方式用来将任务委托给一个ExecutorService:

    execute(Runnable):接受一个runnable对象,异步的去执行它,使用这种方法没有办法获取执行后的runnable结果,如果你希望获取运行之后接受返回值,那么就必须使用接受Callable参数的submit()方法。

    submit(Runnable):接受一个runnable对象,但是会返回一个future对象,这个对象可以用于判断runnable任务是否结束执行。如果使用future的get方法,那么因为runnable没有返回值,所以只能get到null值。

    submit(Callable):接受一个Callable对象,,但是会返回一个future对象,Callable和Runnable相似,只不过Callable中是call方法,Runnable中是run方法,而且Callable中可以有返回值,而Runnable没有返回值,返回的future对象使用get可以获取Callable返回的结果。

    invokeAny(Callable集合):接受一个装有Callable对象的集合,不会返回future对象,而是会返回这个集合中某一个Callable执行后的结果(返回值),如果一个任务运行完毕或者抛出异常,方法会取消其他Callable的执行。

    invokeAll(Callable集合):接受一个装有Callable对象的集合,返回future对象的集合,通过这个future集合可以管理所有Callable的执行结果。不过要注意,任务可能因为异常而导致运行结束,所以他可能不是真的执行结束,我们没办法通过future对象来了解这个差异。

  当我们使用完ExecutorService之后,应该手动关闭它,不然虚拟机仍会保持运行。

  如果你的程序通过main() 方法启动,并且主线程退出了你的程序,如果你还有一个活动的 ExecutorService存在于程序中,那么程序将会继续保持运行状态。存在于ExecutorService中的活动线程会阻止Java虚拟机关闭

  

 1 package com.chinasofti.executor.reS;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 import java.util.concurrent.Callable;
 6 import java.util.concurrent.ExecutorService;
 7 import java.util.concurrent.Executors;
 8 
 9 public class TestExitMain {
10     public static void main(String[] args) throws InterruptedException {
11         ExecutorService executorService = Executors.newCachedThreadPool();
12 
13         List<Callable<String>> list = new ArrayList<Callable<String>>();
14         list.add(()->{
15             System.out.println("zbc5线程还在运行");
16             return "zbc5";
17         });
18         list.add(()->{
19             System.out.println("zbc2线程还在运行");
20             return "zbc2";
21         });
22         list.add(()->{
23             System.out.println("zbc3线程还在运行");
24             return "zbc3";
25         });
26         list.add(()->{
27             System.out.println("zbc4线程还在运行");
28             return "zbc4";
29         });
30         executorService.invokeAll(list);
31         System.out.println("主线程已退出");
32         Thread.currentThread().stop();
33         System.out.println("测试主线程退出后是否输出");
34     }
35 }

 

 

 

  执行结果:

    

 

 

       为了关闭在 ExecutorService 中的线程,需要调用 shutdown() 方法。ExecutorService 并不会马上关闭,而是不再接收新的任务,一旦所有的线程结束执行当前任务,ExecutorServie才会真的关闭。所有在调用shutdown()方法之前提交到ExecutorService的任务都会执行

   如果希望立即关闭ExecutorService,可以调用shutdownNow()方法。它会尝试马上关闭所有正在执行的任务,并且跳过所有已经提交但是还没有运行的任务。但是对于正在执行的任务,是否能够成功关闭它是无法保证的,有可能他们真的被关闭掉了,也有可能它会壹直执行到任务结束

 

 二、Runnable和Callable区别

  Runnable:提供的任务方法为run(),没有返回值

  Callable:提供的任务方法是call(),有返回值

  两个都是符合函数式接口

三、Lock

  Lock其实和synchronized功能相似,只是Lock更具有语义化,可以更加清楚的表达清楚何时加锁何时释放锁。

  JDK1.5中,提供了一个新的所对象Lock(是个接口),默认实现类是:ReentrantLock:

    可重入的独占锁,有着一些synchronized关键字扩展的功能。使用lock加锁,使用unlock解锁。该类有一个重要特性体现在构造器上,构造器接受一个可选参数,是否是公平锁。

    公平锁:

      先来的一定排队,一定先获得锁

    非公平锁:

      不保证上述条件,非公平的吞吐量更高。

 

 

    

 1 package com.chinasofti.executor;
 2 
 3 import java.util.concurrent.locks.Lock;
 4 import java.util.concurrent.locks.ReentrantLock;
 5 
 6 public class LockTest {
 7     public static void main(String[] args) {
 8         Print print = new Print();
 9 
10         Thread t1 = new Thread(()->{
11             try {
12                 print.ma();
13             } catch (InterruptedException e) {
14                 e.printStackTrace();
15             }
16         });
17 
18         Thread t2 = new Thread(()->{
19             try {
20                 print.mb();
21             } catch (InterruptedException e) {
22                 e.printStackTrace();
23             }
24         });
25 
26         Thread t3 = new Thread(()->{
27             try {
28                 print.mc();
29             } catch (InterruptedException e) {
30                 e.printStackTrace();
31             }
32         });
33 
34         t1.start();
35         t2.start();
36         t3.start();
37     }
38 
39 }
40 class Print{
41     Lock lock = new ReentrantLock();
42 
43     public void ma() throws InterruptedException {
44         lock.lock();
45         for (int i = 0; i < 10; i++) {
46             Thread.sleep(500);
47             System.out.println(Thread.currentThread().getName() + "---" + i);
48         }
49         lock.unlock();
50     }
51 
52     public void mb() throws InterruptedException {
53         lock.lock();
54         for (int i = 0; i < 10; i++) {
55             Thread.sleep(500);
56             System.out.println(Thread.currentThread().getName() + "---" + i);
57         }
58         lock.unlock();
59     }
60 
61     public void mc() throws InterruptedException {
62         lock.lock();
63         for (int i = 0; i < 10; i++) {
64             Thread.sleep(500);
65             System.out.println(Thread.currentThread().getName() + "---" + i);
66         }
67         lock.unlock();
68     }
69 }

 

 

 

   ThreadLocal:

      ThreadLocal用来解决多线程间的并发问题(线程间没有各自独立的变量)

    ThreadLoacl不是一个Thread,而是Thread的一个局部变量,当使用ThreadLoacl维护变量时,ThreadLocal为每个使用变量的线程都提供了一个副本变量,所以每个线程都能独立的使用自己的副本变量,不会影响到其他线程的副本变量。

    方法:  

      void set(T value):将此线程局部变量的当前线程副本中的值设置为指定值

      void remove():移除此线程局部变量当前线程的值

      protected T initalValue():返回此线程局部变量的当前线程的“初始值”

      T get():返回此线程中局部变量的当前线程副本中的值

    

 

 

 四、信号量

          

 

    信号量,有时候被称为信号灯,是在多线程环境下使用的一种设施,他负责协调各个线程,以保证他们能够正常、合理的使用公共资源。

    一个计数信号量,从概念上来说,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个请求资源的线程,然后再获取该许可。信号量提供一个方法添加一个许可,从而可释放一个正在阻塞的获取者。

    信号量对可用许可进行计数,并采取相应的行动,拿到信号量许可的线程可以进入代码,否则就等待。通过申请和释放方法获取和释放访问许可。

     

 

 

  

    总结:

    线程池主要是用来解决线程生命周期开销问题和资源不足问题。通过多个任务重复使用线程,线程创建的开销就被分摊到多个任务上,而且由于请求到达时线程已经存在,所以消除了线程创建所带来的延迟,这样就可以更快的响应。

    信号量被称为信号灯,是在多线程下使用的一种设施,他负责协调各个线程,以保证他们能够正确、合理的使用公共资源。一个计数信号量,从概念上来说,信号量维护了一个许可集。如有必要,信号量会阻塞请求许可的资源,然后再获得该许可。信号量提供了一个方法添加一个许可,从而可能释放一个正在阻塞的获取者。

    Executor是一个可以提交可执行任务的工具,这个接口解耦了提交和执行任务的细节,Executor主要来替代显示的创建和运行线程。

    Runnable和Callable都是可以抽象任务供Executor执行,不同的是Runnable没有返回值,Callable有返回值。

    ThreadLoacl用来解决多线程的并发问题,ThreadLoacl并不是一个Thread,而是Thread的一个局部变量,ThreadLoacl在维护局部变量时,ThreadLocal为每个线程提供了一个独立副本,每个线程之间互不干扰。

    

五、Timer

  Timer是Java最早提供的一个任务调度器,它可以支持定时任务和重复任务的调度。

  Timer通过一个独立的线程通过wait/notify机制对所有的任务进行调度,但是用户无需关心内部的线程实现细节,仅需通过Timer类的相关调度方法来实现任务的调度。

  

  Timer调度方法的第一个参数都是TimerTask的对象,TimerTask是所执行任务的代码。

  

 

 

 

  注意,由于Java要考虑跨平台性,又因为每个平台的线程调度机制不同,从而Timer不能保证任务能够按时完成。由于TimerTask是由Runnable接口实现的,在TimerTask被放进线程队列中睡眠一段时间(wait)后,当到了指定唤醒该TimerTask时,由于JVM的调度策略,以及不知道还有多少线程在等待CPU处理,因此不能保证线程执行的时间一定准确。通常有两种情况下导致任务延迟执行:

    1.有大量的线程等待执行

    2.GC机制的影响导致延迟

 1 package com.chinasofti.executor;
 2 
 3 import java.util.Date;
 4 import java.util.Timer;
 5 import java.util.TimerTask;
 6 
 7 public class TimerTest {
 8     public static void main(String[] args) {
 9         Timer timer = new Timer();
10         // 立即执行
11         timer.schedule(new Printt(),new Date());
12         // 延时一秒执行
13         timer.schedule(new Printt2(),1000);
14         // 延时一秒后每隔0.5s重复执行
15         timer.schedule(new Printt(),1000,1000);
16         // 每隔0.5s重复
17         timer.schedule(new Printt2(),new Date(),1000);
18     }
19 
20 
21 }
22 
23 class Printt extends TimerTask {
24     @Override
25     public void run() {
26         System.out.println("print");
27     }
28 }
29 class Printt2 extends TimerTask {
30     @Override
31     public void run() {
32         System.out.println("print222");
33     }
34 }

  

                         

        

 

 

      

 1 package com.chinasofti.executor;
 2 
 3 import java.util.concurrent.ScheduledThreadPoolExecutor;
 4 import java.util.concurrent.TimeUnit;
 5 
 6 public class ScheduledThreadPoolExecutorTest {
 7     public static void main(String[] args) {
 8         ScheduledThreadPoolExecutor stpe = new ScheduledThreadPoolExecutor(5);
 9 
10         // 第一个参数是TimerTask对象,第二个参数是延时时间,第三个参数是延时时间的单位
11         stpe.schedule(()->{
12             System.out.println("哈哈哈哈哈哈哈哈奥或哈奥奥奥奥奥奥奥奥奥奥奥奥奥奥奥奥奥奥奥奥奥奥奥奥奥奥奥奥奥奥");
13         },10, TimeUnit.SECONDS);
14     }
15 }

 

      

  • Quartz是一个完全由java编写的开源任务调度框架。尽管Quartz框架整合了许多额外功能, 但就其简易形式看,它非常易用。简单地创建一个实现org.quartz.Job接口的java类。Job接口包含唯一的方法:

public void execute(JobExecutionContext context) throws JobExecutionException;

  • 在Job接口实现类里面,添加一些业务代码到execute()方法。一旦配置好Job实现类并设定好调度时间表,Quartz将密切注意剩余时间。当调度程序确定该是通知执行任务的时候,Quartz框架将调用Job实现类上的execute()方法并允许做它该做的事情。无需报告任何东西给调度器或调用任何特定的东西。仅仅执行任务和结束任务即可。如果配置你的作业在随后再次被调用,Quartz框架将在恰当的时间再次调用它
  • 而这个过程和Timer/TimerTask的搭配十分类似
  • Quartz框架的核心是调度器。调度器负责管理Quartz应用运行时环境。调度器不是靠自己做所有的工作,而是依赖框架内一些非常重要的部件。Quartz不仅仅是线程和线程管理。为确保可伸缩性,Quartz采用了基于多线程的架构。启动时,框架初始化一套worker线程,这套线程被调度器用来执行预定的作业。这就是Quartz怎样能并发运行多个作业的原理。Quartz依赖一套松耦合的线程池管理部件来管理线程环境

 

 

 

 

 

 

 

 

  总结:

  • Timer和TimerTask是什么关系?
  • Timer和ScheduledThreadPoolExecutor有什么区别?
  • Quartz的功能是什么?
  • Timer是Java最早提供的一个任务调度器,它可以支持定时任务和重复任务的调度,如果希望借助于Timer进行任务调度,必须要满足Timer对任务本身的抽象规定,从刚才的Timer调度方法列表中可以看出,所有的方法第一个共同的参数均为TimerTask的对象,用于声明需要调度的任务中需要执行的指令代码
  • 一个Timer调度器的所有任务都运行在一个线程中,存在单个任务出现异常导致所有任务不能执行的隐患,而JDK5之后的ScheduledThreadPoolExecutor提供了并发任务调用,不存在这个隐患
  • Quartz是一个完全由java编写的开源任务调度框架 

 

posted @ 2020-08-07 20:12  大明湖畔的闰土  阅读(1751)  评论(1编辑  收藏  举报