精华内容
下载资源
问答
  • 线程是进程内一个执行单元,进程至少有一个线程,多线程共享进程地址空间,而进程有自己独立地址空间。 操作系统以进程为单位分配资源,同一个进程内线程,共享进程资源。 线程是处理器调度基本单位,但...

    一、进程与线程

    进程是指内存中运行的应用程序,每个进程都有自己独立的一块内存空间。
    线程是指进程中的一个执行流程,一个进程中可以运行多个线程。
    进程和线程的区别:

    1. 线程是进程内的一个执行单元,进程至少有一个线程,多线程共享进程的地址空间,而进程有自己独立的地址空间。
    2. 操作系统以进程为单位分配资源,同一个进程内的线程,共享进程的资源。
    3. 线程是处理器调度的基本单位,但进程不是。
    1.1线程的生命周期:
    1. 新建状态(New): 新创建一个线程对象。
    2. 就绪状态(Runnable): 线程对象创建后,其他线程嗲用了该对象的start()方法,该状态的线程位于“可运行线程池”中,变得可运行,只等待获取cpu的使用权,即在就绪状态的进程除cpu之外,其它的运行所需要资源都已全部获得。
    3. 运行状态(Running): 就绪状态的线程获取了cpu,执行程序代码。
    4. 阻塞状态(Blocked): 阻塞状态是线程因为某种原因放弃cpu使用权,暂时停止运行,直到线程进入就绪状态,才有机会转到运行状态。
      阻塞状态分为三种:
      A:等待阻塞–运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒。
      B:同步阻塞–运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。
      C:其他阻塞–运行的线程执行sleep()或join()方法,或者发出 I/O 请求时,JVM会把线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时,或者 I/O 处理完毕时,线程重新转入就绪状态。
    5. 死亡状态(Dead): 线程执行了完成或者因异常退出run()方法,该线程结束生命周期。
    1.2 状态转换图如下:

    在这里插入图片描述

    1.3、wait和sleep的区别

    1. sleep是Thread类的方法,是线程用来控制自身流程的,调用sleep(),可以设置睡眠时间,在设定的时间后会继续执行。
    2. wait是Object类的方法,用来线程间的通信,这个方法会使当前拥有该对象锁的进程等待知道其他线程调用notify()或者notigyAll()唤醒wait等待的线程.
    3. 调用sleep方法不会释放锁(自己的感觉是sleep方法本来就是和锁没有关系的,因为他是一个线程用于管理自己的方法,不涉及线程通信)
    4. 最大的不同是在等待时 wait 会释放锁,而 sleep 一直持有锁。 wait 通常被用于线程间交互, sleep 通常被用于暂停执行。

    1.4、sleep和yield的区别

    1. sleep()方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级;但yield()方法只会给优先级相同,或优先级更高的线程执行机会。
    2. sleep()方法会将线程转入阻塞状态,直到经过阻塞时间才会转入就绪状态;而yield()方法不会将线程转入阻塞状态,它只是强制当前线程进入就绪状态。因此完全有可能某个线程调用yield()方法暂停之后,立即再次获得处理器资源被执行。
    3. sleep()方法声明抛出了InterruptedException异常,所以调用sleep()方法时要么捕捉该异常,要么显式声明抛出该异常;而yield()方法则没有声明抛出任何异常。
    4. sleep()方法比yield()方法有更好的可移植性,通常不建议使用yield()方法来控制并发线程的执行。

    二、多线程

    2.1什么是多线程?

    多线程就是指一个进程中同时有多个执行路径(线程)正在执行。

    2.2为什么要使用多线程?
    1. 在一个程序中,有很多的操作是非常耗时的,如数据库读写操作,IO操作等,如果使用单线程,那么程序就必须等待这些操作执行完成之后才能执行其他操作。使用多线程,可以在将耗时任务放在后台继续执行的同时,同时执行其他操作。

    2. 可以提高程序的效率。

    3. 在一些等待的任务上,如用户输入,文件读取等,多线程就非常有用了。

    2.3多线程的实现方式:
    1. 继承 Thread 类: 但 Thread 本质上也是实现了 Runnable 接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过 Thread 类的 start()实例方法。 start()方法是一个native 方法,它将启动一个新线程,并执行 run()方法。这种方式实现多线程很简单,通过自己的类直接 extend Thread,并复写 run()方法,就可以启动新线程并执行自己定义的 run()方法。例如: 继承 Thread 类实现多线程,并在合适的地方启动线程
    1. public class MyThread extends Thread {
    2. public void run() {
    3. System.out.println("MyThread.run()");
    4.	 }
    5. }
    6. MyThread myThread1 = new MyThread();
    7. MyThread myThread2 = new MyThread();
    8. myThread1.start();
    9. myThread2.start();
    
    1. 实现 Runnable 接口的方式实现多线程,并且实例化 Thread,传入自己的 Thread 实例,调用 run( )方法
    1. public class MyThread implements Runnable {
    2. public void run() {
    3. System.out.println("MyThread.run()");
    4.	 }
    5. }
    6. MyThread myThread = new MyThread();
    7. Thread thread = new Thread(myThread);
    8. thread.start();
    
    1. 通过Callable和Future创建有返回值的多线程
      首先定义一个类实现Callable接口,重写call()方法,然后在启动类中new一个FutureTask,将实现Callable接口的实现类的实例放进去,然后new一个Thread,将FutureTask实例放进去,调用start()方法启动。
    有返回值的多线程案例:

    有返回值的任务必须实现 Callable 接口,类似的,无返回值的任务必须 Runnable 接口。执行 Callable 任务后,可以获取一个 Future 的对象,在该对象上调用 get 就可以获取到 Callable 任务返回的 Object 了,再结合线程池接口ExecutorService 就可以实现传说中有返回结果的多线程了。

    1. import java.util.concurrent.*;
    2. import java.util.Date;
    3. import java.util.List;
    4. import java.util.ArrayList;
    5.
    6. /**
    7. * 有返回值的线程
    8. */
    9. @SuppressWarnings("unchecked")
    10. public class Test {
    11. public static void main(String[] args) throws ExecutionException,
    12. InterruptedException {
    13. System.out.println("----程序开始运行----");
    14. Date date1 = new Date();
    15.
    16. int taskSize = 5;
    17. // 创建一个线程池
    18. ExecutorService pool = Executors.newFixedThreadPool(taskSize);
    19. // 创建多个有返回值的任务
    20. List<Future> list = new ArrayList<Future>();
    21. for (int i = 0; i < taskSize; i++) {
    22. Callable c = new MyCallable(i + " ");
    23. // 执行任务并获取 Future 对象
    24. Future f = pool.submit(c);
    25. // System.out.println(">>>" + f.get().toString());
    26. list.add(f);
    27. }
    28. // 关闭线程池
    29. pool.shutdown();
    30.
    31. // 获取所有并发任务的运行结果
    32. for (Future f : list) {
    33. // 从 Future 对象上获取任务的返回值,并输出到控制台
    34. System.out.println(">>>" + f.get().toString());
    35. }
    36.
    37. Date date2 = new Date();
    38. System.out.println("----程序结束运行----,程序运行时间【"
    39. + (date2.getTime() - date1.getTime()) + "毫秒】 ");
    40. }
    41. }
    42.
    43. class MyCallable implements Callable<Object> {
    44. private String taskNum;
    45.
    46. MyCallable(String taskNum) {
    47. this.taskNum = taskNum;
    48. }
    49.
    50. public Object call() throws Exception {
    51. System.out.println(">>>" + taskNum + "任务启动");
    52. Date dateTmp1 = new Date();
    53. Thread.sleep(1000);
    54. Date dateTmp2 = new Date();
    55. long time = dateTmp2.getTime() - dateTmp1.getTime();
    56. System.out.println(">>>" + taskNum + "任务终止");
    57. return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】 ";
    58. }
    59. }
    

    三、线程池

    3.1什么是线程池?

    线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。我们知道线程的创建和销毁相对于线程的执行来说是比较消耗资源的,我们可以用线程池缓存线程,用已有的闲置线程来执行新任务。也可以避免线程并发数量过多,抢占系统资源从而导致阻塞。

    3.2在Java中,线程池有一个最大的父接口Executor,而它的具体实现为ThreadPoolExecutor类,ThreadPoolExecutor类中提供了四种构造函数,这些构造函数里有这些参数:

    corePoolSize:核心线程数
    maximumPoolSize:最大线程数
    keepAliveTime:该线程池中非核心线程闲置超时时长 TimeUnit:keepAliveTime的单位
    BlockingQueue:该线程池中的任务队列:维护着等待执行的Runnable对象
    ThreadFactory:线程工厂,工作线程创建时使用的工厂,new他的时候需要实现他的Thread newThread(Runnable)方法。
    RejectedExecutionHandler:拒绝策略,是一个回调函数,当线程池中队列已满而且线程数也已经达到maximumPoolSize时,线程池就会拒绝接收任务并且调用这个回调函数,
    通过ThreadPoolExecutor.execute(Runnable command)方法即可向线程池内添加任务。
    如果不想自己创建线程池的话,也可以使用Executors,Java通过Executors提供了四种线程池,这四种线程池都是直接或间接配置ThreadPoolExecutor的参数实现的,
    newCachedThreadPool() 可缓存线程池: 线程数无限制,有空闲线程则复用空闲线程,若无空闲线程则新建线程
    newFixedThreadPool() 定长线程池:,可控制线程最大并发数(同时执行的线程数) 超出的线程会在队列中等待
    newScheduledThreadPool() 定长线程池: 支持定时及周期性任务执行。
    newSingleThreadExecutor() 单线程化的线程池: 有且仅有一个工作线程执行任务,所有任务按照指定顺序执行,即遵循队列的入队出队规则

    创建方法:ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

    3.3对线程池的理解??

    ①:如何使用?

    线程池是多线程的一种处理形式,它事先把多个线程对象放在一个容器当中,使用时不用new线程,而是直接去线程池中去拿,节省了开辟子线程空间,降低线程多引起的并发。

    ②:好处:

    降低资源消耗、提高响应速度(不需要创建线程就执行)、提高线程可管理性—>线程池可统一分配、调优和监控。

    ③:启动策略:

    1. 线程池刚创建时,里面没有一个线程,任务队列是作为参数传递进来的。
    2. 调用execute()方法,添加一个任务,线程池会对其进行判断:< 以corePoolSize为界>
      A:当前运行线程数小于corePoolSize,会立马创建线程执行任务。
      B:大于或等于corePoolSize,将任务放入队列。
      C:队列满了,并且正在运行线程数小于 maximumPoolSize,继续创建线程进行任务,如果大于或等于maximumPoolSize,抛异常不再接受任务。
    3. 完成任务时,会从队列中取出下一个任务执行。
    4. 当线程无事可做,超过一定时间,线程池会对其进行判断,如果当前运行线程大于corePoolSize,线程就被停掉,任务完成后,收缩到corePoolSize大小。
    展开全文
  • 关于线程和线程池的学习,我们可以从以下几个方面入手: 第一,什么是线程,线程和进程的区别是什么 第二,线程中的基本概念,线程的生命周期 第三,单线程和多线程 第四,线程池的原理解析 第五,常见的几种...

    本文章转自他人,之前有些不通顺错别字的地方,我对其进行了修改,写的不错,转载一下。

    关于线程和线程池的学习,我们可以从以下几个方面入手:

    第一,什么是线程,线程和进程的区别是什么

    第二,线程中的基本概念,线程的生命周期

    第三,单线程和多线程

    第四,线程池的原理解析

    第五,常见的几种线程池的特点以及各自的应用场景

    一、

    线程,程序执行流的最小执行单位,是行程中的实际运作单位,经常容易和进程这个概念混淆。那么,线程和进程究竟有什么区别呢?首先,进程是一个动态的过程,是一个活动的实体。简单来说,一个应用程序的运行就可以被看做是一个进程,而线程,是运行中的实际的任务执行者。可以说,进程中包含了多个可以同时运行的线程。

    二、

    线程的生命周期,线程的生命周期可以利用以下的图解来更好的理解:

    第一,是用new Thread()的方法新建一个线程,在线程创建完成之后,线程就进入了就绪(Runnable)状态,此时创建出来的线程进入抢占CPU资源的状态,当线程抢到了CPU的执行权之后,线程就进入了运行状态(Running),当该线程的任务执行完成之后或者是非常态的调用的stop()方法之后,线程就进入了死亡状态。

    第二,我们在图解中可以看出,线程还具有一个阻塞的过程,这是怎么回事呢?当面对以下几种情况的时候,容易造成线程阻塞,第一种,当线程主动调用了sleep()方法时,线程会进入则阻塞状态,除此之外,当线程中主动调用了阻塞时的IO方法时,这个方法有一个返回参数,当参数返回之前,线程也会进入阻塞状态,还有一种情况,当线程进入正在等待某个通知时,会进入阻塞状态。

    第三,那么,为什么会有阻塞状态出现呢?我们都知道,CPU的资源是十分宝贵的,所以,当线程正在进行某种不确定时长的任务时,Java就会收回CPU的执行权,从而合理应用CPU的资源。我们根据图可以看出,线程在阻塞过程结束之后,会重新进入就绪状态,重新抢夺CPU资源。这时候,我们可能会产生一个疑问,如何跳出阻塞过程呢?由以上几种可能造成线程阻塞的情况来看,都是存在一个时间限制的,当sleep()方法的睡眠时长过去后,线程就自动跳出了阻塞状态,第二种则是在返回了一个参数之后,在获取到了等待的通知时,就自动跳出了线程的阻塞过程

    三、

    什么是单线程和多线程?

    单线程,顾名思义即是只有一条线程在执行任务,这种情况在我们日常的工作学习中很少遇到,所以我们只是简单做一下了解

    多线程,创建多条线程同时执行任务,这种方式在我们的日常生活中比较常见。但是,在多线程的使用过程中,还有许多需要我们了解的概念。比如,在理解上并行和并发的区别,以及在实际应用的过程中多线程的安全问题,对此,我们需要进行详细的了解。

    并行和并发:在我们看来,都是可以同时执行多种任务,那么,到底他们二者有什么区别呢?

    并发,从宏观方面来说,并发就是同时进行多种时间,实际上,这几种时间,并不是同时进行的,而是交替进行的,而由于CPU的运算速度非常的快,会造成我们的一种错觉,就是在同一时间内进行了多种事情

    而并行,则是真正意义上的同时进行多种事情。这种只可以在多核CPU的基础下完成。

    还有就是多线程的安全问题?为什么会造成多线程的安全问题呢?我们可以想象一下,如果多个线程同时执行一个任务,那么意味着他们共享同一种资源,由于线程CPU的资源不一定可以被谁抢占到,这时,第一条线程先抢占到CPU资源,他刚刚进行了第一次操作,而此时第二条线程抢占到了CPU的资源,那么,共享资源还来不及发生变化,就同时有两条数据使用了同一条资源,具体请参考多线程买票问题。这个问题我们应该如何解决那?(C++里面个人经验告诉我的做法是锁住线程,然后等待此线程处理完毕后,再进行解锁,这样另一个线程才能操作这个资源)

      有造成问题的原因我们可以看出,这个问题主要的矛盾在于,CPU的使用权抢占和资源的共享发生了冲突,解决时,我们只需要让一条线程占据了CPU的资源时,阻止第二条线程同时抢占CPU的执行权,在代码中,我们只需要在方法中使用同步代码块即可。在这里,同步代码块不多进行赘述,可以自行了解。(原来java里面锁线程的方法叫同步代码块?需要了解)

    四,线程池

    又以上介绍我们可以看出,在一个应用程序中,我们需要多次使用线程,也就意味着,我们需要多次创建并销毁线程。而创建并销毁线程的过程势必会消耗内存。而在Java中,内存资源是及其宝贵的,所以,我们就提出了线程池的概念。

    线程池:Java中开辟出了一种管理线程的概念,这个概念叫做线程池,从概念以及应用场景中,我们可以看出,线程池的好处,就是可以方便的管理线程,也可以减少内存的消耗。

    那么,我们应该如何创建一个线程池那?Java中已经提供了创建线程池的一个类:Executor

    而我们创建时,一般使用它的子类:ThreadPoolExecutor.

    public ThreadPoolExecutor(int corePoolSize,  
                                  int maximumPoolSize,  
                                  long keepAliveTime,  
                                  TimeUnit unit,  
                                  BlockingQueue<Runnable> workQueue,  
                                  ThreadFactory threadFactory,  
                                  RejectedExecutionHandler handler)


    这是其中最重要的一个构造方法,这个方法决定了创建出来的线程池的各种属性,下面依靠一张图来更好的理解线程池和这几个参数:

    又图中,我们可以看出,线程池中的corePoolSize就是线程池中的核心线程数量,这几个核心线程,只是在没有用的时候,也不会被回收,maximumPoolSize就是线程池中可以容纳的最大线程的数量,而keepAliveTime,就是线程池中除了核心线程之外的其他的最长可以保留的时间,因为在线程池中,除了核心线程即使在无任务的情况下也不能被清除,其余的都是有存活时间的,意思就是非核心线程可以保留的最长的空闲时间,而util,就是计算这个时间的一个单位,workQueue,就是等待队列,任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出)。threadFactory,就是创建线程的线程工厂,最后一个handler,是一种拒绝策略,我们可以在任务满了之后,拒绝执行某些任务。

    线程池的执行流程又是怎样的呢?

    有图我们可以看出,任务进来时,首先执行判断,判断核心线程是否处于空闲状态,如果不是,核心线程就先就执行任务,如果核心线程已满,则判断任务队列是否有地方存放该任务,若果有,就将任务保存在任务队列中,等待执行,如果满了,在判断最大可容纳的线程数,如果没有超出这个数量,就开创非核心线程执行任务,如果超出了,就调用handler实现拒绝策略。

    handler的拒绝策略:

    有四种:第一种AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满

                 第二种DisCardPolicy:不执行新任务,也不抛出异常

                 第三种DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行

                 第四种CallerRunsPolicy:直接调用execute来执行当前任务

    五,四种常见的线程池:

    CachedThreadPool:可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。

    SecudleThreadPool:周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。

    SingleThreadPool:只有一条线程来执行任务,适用于有顺序的任务的应用场景。

    FixedThreadPool:定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程

    展开全文
  • 线程池客服热线案例引入线程池1.线程的概念2.线程池的作用:获取线程池... 对于线程池的理解可以类比成用户给运行商的客服打电话,假设某运营商客服作息500人,现有500用户正与客服一一交流,那么第501个用户即海...


     尼采说过,人类无法理解没有经历过的事情。所以很多概念不能去强行地理解和记忆,需要结合实际生活中的案例。

    客服热线案例

     对于线程池的理解可以类比成用户给运行商的客服打电话,假设某运营商客服作息500人,现有500用户正与客服一对一交流,那么第501个用户即海绵宝宝就会听到语音等待:“您好!现在作息繁忙…”,海绵宝宝只能等待,如果说以上500个用户有一个挂断电话,海绵宝宝都可以成功连上人工客服;
     用户电话打进来对客服来说都是一项任务,而每个客服可以完成多个任务,而不是仅仅500个任务,比如每个客服每天接入100个电话,那么整个客服后台就是完成了5万个任务;

    在这里插入图片描述

    类比:这里客服后台可以看成是线程池,500个客服可以看成是500个线程,500个用户就是Task,而接通电话这件事情就是线程的执行;第501个拨打客服热线的用户相当于501个Task,超过最大值的线程可以排队,但他们要等到其他线程完成后才启动;这就实现了对线程的复用;

    类比这个现实案例,可以发现存在以下问题:

    • 线程是宝贵的内存资源、单个的线程约占1MB空间。过多分配容易造成内存溢出。
    • 频繁的创建及销毁线程会增加虚拟机回收频率、资源的开销,造成程序性能下降

    引入线程池

    1.线程的概念

    以下内容摘自百度百科

     线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。

    2.线程池的作用:

    • 将任务提交给线程池。由线程池分配线程、运行任务,并在当前任务结束后复用线程。
    • 线程容器。可设定线程分配数量的上限。
    • 将预先创建好的线程对象存入池中,并重用线程池中的线程对象。
    • 避免频繁的创建和销毁线程。

    获取线程池

    1.常用的线程池接口和类

    • Executor : 线程池的顶级接口
    方法 描述
    void execute(Runnable command) 在将来的某个时间执行给定的命令。
    • ExecutorService:
       线程池接口,继承了Executor ,并增加和优化了父接口的一些行为,可通过submit(Runnable task)提交任务代码。
    方法 描述
    Future submit(Callable task) 提交值返回任务以执行,并返回代表任务待处理结果的Future。
    Future<?> submit(Runnable task) 提交一个可运行的任务执行,并返回一个表示该任务的未来。
    Future submit(Runnable task, T result) 提交一个可运行的任务执行,并返回一个表示该任务的未
    • Executors工厂类:

     通过下面的方法可以获得一个固定线程数量的线程池。参数即线程池中线程的数量

    方法 描述
    static ExecutorService newFixedThreadPool(int nThreads) 创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。

    创建方式

    //线程池引用 ===> Executors工具类创建线程
            ExecutorService executorService = Executors.newFixedThreadPool(2);
    

     也可以通过newCachedThreadPool() 获取动态数量的线程池,如果不够则自动创建新的,没有上限;

    JavaApi:

    创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程。 这些池通常会提高执行许多短暂异步任务的程序的性能。 调用execute将重用以前构造的线程(如果可用)。 如果没有可用的线程,将创建一个新的线程并将其添加到该池中。 未使用六十秒的线程将被终止并从缓存中删除。 因此,长时间保持闲置的池将不会消耗任何资源。 请注意,可以使用ThreadPoolExecutor构造函数创建具有相似属性但不同详细信息的池(例如,超时参数)。

    方法 描述
    static ExecutorService newCachedThreadPool() 创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程。

    2.代码案例

     现有一个任务,实现Runnable接口,循环打印5次,交有一个线程池executorService去分别执行任务,且线程池的最大线程数为2 ;

    public class TestThreadPool {
        public static void main(String[] args){
    
            //线程池引用 =  Executors工具类创建线程
            ExecutorService executorService = Executors.newFixedThreadPool(2);
    
            Runnable runnable = new MyTask();
            executorService.submit(runnable);
        }
    }
    class MyTask implements Runnable{
    
        @Override
        public void run() {
            for (int i = 0; i <5; i++) {
                System.out.println(Thread.currentThread().getName()+" MyTask: "+i);
            }
        }
    }
    

    打印结果:
    在这里插入图片描述
    如果一个任务被提交多次的?比如提交给线程池的任务大于线程池线程的最大上限,会不会
    出现线程的复用呢?
    测试方法:

    //线程池引用 =  Executors工具类创建线程
     ExecutorService executorService = Executors.newFixedThreadPool(2);
     Runnable runnable = new MyTask();
     executorService.submit(runnable);
     executorService.submit(runnable);
     executorService.submit(runnable);
    

     显然现在有两个线程却提交了三个任务,因此,必然有一个线程要被复用。

    打印结果:
    在这里插入图片描述
     从打印结果来看,线程1被复用了两次,避免了线程频繁的创建和销毁,达到了优化的目的;

    Callable接口

    1.概念简述

     前面在介绍Executor的时候,列出了以下三个方法,其中第一个方法的参数是Callable

    方法 描述
    Future submit(Callable task) 提交值返回任务以执行,并返回代表任务待处理结果的Future。
    Future<?> submit(Runnable task) 提交一个可运行的任务执行,并返回一个表示该任务的未来。
    Future submit(Runnable task, T result) 提交一个可运行的任务执行,并返回一个表示该任务的未
    @FunctionalInterface
    public interface Callable<V>
    

     返回结果并可能引发异常的任务。 实现者定义一个没有参数的单一方法,称为call 。
    Callable接口类似于Runnable ,因为它们都是为其实例可能由另一个线程执行的类设计的。 然而,A Runnable不返回结果,也不能抛出被检查的异常。

    2.应用场景

     假如我们需要计算1+2+3+......+1000000的结果,这个过程我们需要交付给多个线程去分别执行,如Thread1执行前10万数字之和,如Thread2执行10万~20万数字之和…,但是其中涉及一个问题,就是Thread1执行执行的结果必须有返回值,不然Thread2不能根据1的结果继续执行;显然,此时就不能使用类实现Runnable接口覆盖run方法的形式来解决了,因为run返回的是void;

    class MyTask implements Runnable{
        @Override
        public void run() {
          //1+2+...+100000
        }
    }
    

    这就用到了Callable,因为它最大的特点就是:返回结果并可能引发异常的任务。
    在这里插入图片描述

    3.方法

    方法 描述
    V call() 计算一个结果,如果不能这样做,就会抛出一个异常。
    • JDK5加入,与Runnable接口类似,实现之后代表一个线程任务。
    • Callable具有泛型返回值、可以声明异常。
    public interface Callable<V>{
    	public V call() throw Exception;
    }
    

    4.应用方法

    public class TestCallable {
        public static void main(String[] args){
            //创建线程池
            ExecutorService executorService = Executors.newFixedThreadPool(2);
            Callable task = new Task();
            executorService.submit(task);
            executorService.submit(task);
            executorService.submit(task);
        }
    }
    class Task implements Callable<String>{
        @Override
        public String call() throws Exception {
            for (int i = 0; i <5 ; i++) {
                System.out.println(Thread.currentThread().getName()+" : "+i);
            }
            return null;
        }
    }
    

    打印结果:
    在这里插入图片描述
     显然现在有两个线程却提交了三个任务,因此,线程2复用了。

    Future 接口

    1.引入

    &emsp;会到我们开始引入的案例:计算1+2+3+......+1000000的结果,这个过程我们需要交付给多个线程去分别执行,Callable可以返回类型,但是怎么将各个线程的返回类型一次相加呢?这里就需要引入Future接口,该对象里面封装了call()方法的返回值

    JavaApi:

    A Future计算的结果。 提供方法来检查计算是否完成,等待其完成,并检索计算结果。 结果只能在计算完成后使用方法get进行检索,如有必要,阻塞,直到准备就绪。 取消由cancel方法执行。 提供其他方法来确定任务是否正常完成或被取消。 计算完成后,不能取消计算。 如果您想使用Future ,以便不可撤销,但不提供可用的结果,则可以声明Future<?>表格的类型,并返回null作为基础任务的结果。

    2.方法

    方法 描述
    boolean cancel(boolean mayInterruptIfRunning) 尝试取消执行此任务。
    V get() 等待计算完成,然后检索其结果。
    V get(long timeout, TimeUnit unit) 如果需要等待最多在给定的时间计算完成,然后检索其结果(如果可用)。
    boolean isCancelled() 如果此任务在正常完成之前被取消,则返回 true 。
    boolean isDone() 返回 true如果任务已完成。

    3.解决分任务执行1+2+…+100的问题

    public class TestCallable {
    	public static void main(String[] args) throws InterruptedException, ExecutionException {
    		System.out.println("程序开始");
    		//创建线程池
    		ExecutorService es = Executors.newFixedThreadPool(3);
    		
    		Callable<Integer> task1 = new MyTask1();
    		Callable<Integer> task2 = new MyTask2();
    		//接收两个任务的返回值
    		Future<Integer> f1 = es.submit(task1);
    		Future<Integer> f2 = es.submit(task2);
    		//检索计算结果并返回给变量
    		Integer result1 = f1.get();//无限期等待
    		Integer result2 = f2.get();//无限期等待
    		//输出结果
    		System.out.println("result1:"+result1 + " + result2:"+result2 +" = " +(result1+result2));		
    	}
    }
    //线程1计算1+2+....+50
    class MyTask1 implements Callable<Integer>{
    	@Override
    	public Integer call() throws Exception {
    	  	Thread.sleep(5000);
    		Integer sum = 0;
    		for (int i = 1; i <= 50; i++) {
    			sum += i;
    		}
    		return sum;
    	}
    }
    //线程2计算51—+52+.....+100
    class MyTask2 implements Callable<Integer>{
    	@Override
    	public Integer call() throws Exception {
    	    Thread.sleep(5000);
    		Integer sum = 0;
    		for (int i = 51; i <= 100; i++) {
    			sum += i;
    		}
    		return sum;
    	}
    }
    

    打印结果(这是动图):
    在这里插入图片描述
     从打印结果来看V get()是以阻塞的形式等待Future中的异步(多线程并发)处理结果(call()的返回值);通俗的来说,这里让两个线程休眠了5秒,因此在两个线程限期等待的过程中,V get()是以阻塞的形式等待线程执行完毕,完后才能拿到Future的返回值;


     其实,在这里已经很接近分布式的感觉啦~
    在这里插入图片描述

    展开全文
  • 一个故事帮你理解线程和线程池

    千次阅读 2016-06-08 16:32:37
    一个故事帮你理解线程和线程池 2016-05-24 阅读:11,791 阅读原文 我是一个线程, 我一出生就被编了个号: 0×3704, 然后被领到一个昏暗屋子里, 这里我发现了很和我一模一样同伴。  我身边同伴0×...

    转自:http://easygeek.com.cn/article/2Qf6Fj.html

    一个故事帮你理解线程和线程池

    2016-05-24   阅读:11,791   阅读原文

    我是一个线程, 我一出生就被编了个号: 0×3704,  然后被领到一个昏暗的屋子里,  这里我发现了很多和我一模一样的同伴。

     我身边的同伴0×6900 待的时间比较长, 他带着沧桑的口气对我说:

    我们线程的宿命就是处理包裹。 把包裹处理完以后还得马上回到这里,否则可能永远回不来了。

    我一脸懵懂,包裹,什么包裹?

    ”不要着急,马上你就会明白了, 我们这里是不养闲人的。“

    果然,没多久,屋子的门开了, 一个面貌凶恶的家伙吼道:

    "0×3704 ,出来!"

    我一出来就被塞了一个沉甸甸的包裹,上面还有附带着一个写满了操作步骤的纸。

    "快去,把这个包裹处理了。"

    "去哪儿处理"

    "跟着指示走, 先到就绪车间"

    果然,地上有指示箭头,跟着它来到了一间明亮的大屋子,这里已经有不少线程了, 大家都很紧张,好像时刻准备着往前冲。

    我刚一进来,就听见广播说:“0×3704, 进入车间”

    我赶紧往前走, 身后很多人议论说:

    ”他太幸运了, 刚进入就绪状态就能运行“

    ”是不是有关系?“

    ”不是,你看人家的优先级多高啊, 唉“

    前边就是车间, 这里简直是太美了, 怪不得老线程总是唠叨着说:要是能一直待在这里就好了。

    这里空间大,视野好,空气清新,鸟语花香,还有很多从来没见过的人,像服务员一样等着为我服务。

    他们也都有编号, 更重要的是每个人还有个标签,上面写着:硬盘,数据库,内存,网卡...

    我现在理解不了,看看操作步骤吧:

    第一步:从包裹中取出参数  

    打开包裹, 里边有个HttpRequest  对象, 可以取到 userName, password两个参数

    第二步:执行登录操作

    奥,原来是有人要登录啊,我把userName/password 交给 数据库服务员,他拿着数据, 慢腾腾的走了。

     

    他怎么这么慢? 不过我是不是正好可以在车间里多待一会儿? 反正也没法执行第三步。

    就在这时,车间里的广播响了: 

    “0×3704,  我是CPU , 记住你正在执行的步骤, 马上带包裹离开”

    我慢腾腾的开始收拾

    ”快点, 别的线程马上就要进来了“

    离开这个车间, 又来到一个大屋子,这里很多线程慢腾腾的在喝茶,打牌。

    ”哥们,你们没事干了?“

    ”你新来的把, 你不知道我在等数据库服务员给我数据啊! ,据说他们比我们慢好几十万倍, 在这里好好歇吧“

    ”啊? 这么慢?  我这里有人在登录系统, 能等这么长时间吗”

    ”放心,你没听说过人间一天, CPU一年吗, 我们这里是用纳秒,毫秒计时的, 人间等待一秒,相当于我们好几天呢, 来的及“

    干脆睡一会吧 , 不知道过了多久 ,大喇叭又开始广播了:

    “0×3704, 你的数据来了,快去执行”

    我转身就往CPU车间跑,发现这里的们只出不进!

    后面传来阵阵哄笑声:

    ”果然是新人, 不知道还得去就绪车间等“

    于是赶紧到就绪车间, 这次没有那么好运了, 等了好久才被再次叫进CPU车间。

    在等待的时候, 我听见有人小声议论:

    ”听说了吗,最近有个线程被kill掉了“

    ”为啥啊?“

    ”这家伙赖在CPU车间不走, 把CPU利用率一直搞成100%,后来就被kill掉了“

    ”Kill掉以后弄哪儿去了“

    ”可能被垃圾回收了吧“

    我心里打了个寒噤 ,  赶紧接着处理, 收下的动作块多了,第二步登录成功了

    第三步:构建登录成功后的主页

    这一步有点费时间, 因为有很多HTML需要处理, 不知道代码谁写的,处理起来很烦人。

    我正在紧张的制作html呢, CPU有开始叫了:

    “0×3704,  我是CPU , 记住你正在执行的步骤, 马上带包裹离开”

    ”为啥啊“

    ”每个线程只能在CPU上运行一段时间,到了时间就得让别人用了, 你去就绪车间待着, 等着叫你吧“

    就这样, 我一直在就绪-运行 这两个状态,不知道轮转了多少次, 终于安装步骤清单把工作做完了。

    最后顺利的把包含html的包裹发了回去。

    至于登录以后干什么事儿 , 我就不管了。

    马上就要回到我那昏暗的房间了, 真有点舍不得这里。

    不过相对于有些线程, 我还是幸运的, 他们运行完以后就彻底的销毁了,而我还活着 !

    回到了小黑屋, 老线程0×6900 问:

    ”怎么样?第一天有什么感觉?“

    ”我们的世界规则很复杂 , 首先你不知道什么时候会被挑中执行;  第二 ,在执行的过程中随时可能被打断,让出CPU车间;

    第三,一旦出现硬盘,数据库这样耗时的操作也得让出CPU,去等待;  第四,就是数据来了,你也不一定马上执行,还得等着CPU挑选“

    ”小伙子理解的不错啊“

    ”我不明白为什么很多线程都执行完就死了, 为什么咱们还活着?“

    ”你还不知道, 长生不老是我们的特权, 我们这里有个正式的名称,叫做 线程池!“

    平淡的日子就这么一天天过去, 作为一个线程, 我每天的生活都是取包裹,处理包裹,然后回到我们昏暗的家:线程池。

    有一天我回来的时候, 听到有个兄弟说, 今天要好好休息下,明天就是最疯狂的一天。

    我看了一眼日历,明天是 11月11号 。

    果然,零点刚过,不知道那些人类怎么了, 疯狂的投递包裹, 为了应付蜂拥而至的海量包裹, 线程池里没有一个人能闲下来,全部出去处理包裹,CPU车间利用率超高,硬盘在嗡嗡转, 网卡疯狂的闪,  即便如此, 还是处理不完,堆积如山。

    我们也没有办法,实在是太多太多了, 这些包裹中大部分都是浏览页面,下订单,买,买,买。

    不知道过了多久, 包裹山终于慢慢的消失了。

    终于能够喘口气, 我想我永远都不会忘记这一天。

    通过这个事件,我明白了我所处的世界:这是一个电子商务的网站!

    我每天的工作就是处理用户的登录,浏览, 购物车,下单,付款。

    我问线程池的元老0×6900 : ” 我们要工作到什么时候?”

    ” 要一直等到系统重启的那一刻”, 0×6900 说

    ” 那你经历过系统重启吗?”

    ” 怎么可能? , 系统重启就是我们的死亡时刻, 也就是世界末日,一旦重启, 整个线程池全部销毁,时间和空间全部消失,一切从头再来”

    ” 那什么时候会重启?”

    ” 这就不好说了,好好享受眼前的生活吧…..”

    其实生活丰富多彩, 我最喜欢的包裹是上传图片,由于网络慢,所以能在就绪车间, CPU车间待很长很长时间,可以认识很多好玩的线程。

    比如说上次认识了memecached 线程,他给我说通过他缓存了很多的用户数据, 还是分布式的! 很多机器上都有!

    我说怪不得后来的登录操作快了那么多, 原来是不再从数据库取数据了你那里就有啊, 哎对了你是分布式的你去过别的机器没有?

    他说怎么可能我每次也只能通过网络往那个机器发送一个GET, PUT命令才存取数据而已, 别的一概不知。

    再比如说上次在等待的时候遇到了数据库连接的线程, 我才知道它他那里也是一个连接池, 和我们线程池几乎一模一样。

    他说有些包裹太变态了,竟然查看一年的订单数据, 简直把我累死了。

    我说拉倒吧你, 你那是纯数据, 你把数据传给我以后,我还得组装成HTML,  工作量不知道比你大多少倍。

    他说一定你要和memecached搞好关系,直接从他那儿拿数据,尽量少直接调用数据库, 我们JDBC connection也能活的轻松点。

    我说好啊好啊, 关键是你得提前把数据搞到缓存啊, 要不然我先问一遍缓存, 没有数据, 我这不还得找你吗?

    生活就是这样, 如果你自己不找点乐子,还有什么意思?

    有一天我遇到一个可怕的事情, 差一点死在外边,回不了线程池了……

    其实这次遇险我应该能够预想到才对, 太大意了。

    前几天我处理过一些从http 发来的存款和取款的包裹, 老线程0×6900 特意嘱咐我:

    “处理这些包裹的时候要特别小心, 你得一定要先获得一把锁, 在对账户存款或者取款的时候一定要把账户给锁住, 要不然别的线程就会在你等待的时候趁虚而入,搞破坏, 我年轻那会儿很毛糙,就捅了篓子”

    为了“恐吓”我, 好心的0×6900还给了我两个表格:

    1、没有加锁的情况

    thread-01

     

    2、加锁的情况

    thread-02

     

    我看的胆颤心惊, 原来不加锁会带来这么严重的事故。

    从此以后看到存款,取款的包裹就倍加小心, 还好,没有出过事故。

    今天我收到的一个包裹是转账, 从某著名演员的账号给某著名导演赚钱, 具体是谁我就不透漏了, 数额可真是不小

    我按照老线程的吩咐, 肯定要加锁啊, 先对著名演员账号加锁, 在对著名导演账号加锁。

    可我万万没想到的是, 还有一个线程,对,就是0×7954,  竟然同时在从这个导演到往这个演员转账。

    于是乎,就出现了这么个情况:

    thread-03

    刚开始我还不知道什么情况, 一直坐在等待车间傻等, 可是等的时间太长了, 长达几十秒 ! 我可从来没有经历过这样的事件。

    这时候我就看到了线程0×7954 , 他悠闲的坐在那里喝咖啡, 我和他聊了起来:

    “哥们, 我看你已经喝了8杯咖啡了, 怎么还不去干活?”

    “你不喝了9杯茶了吗?” 0×7954 回敬到。

    “我在等一个锁, 不知道哪个孙子一直不释放”

    “我也在等锁啊,我要是知道哪个孙子不释放锁我非揍死他不可 ” 0×7954 毫不示弱。

    我偷偷的看了一眼, 这家伙怀里不就抱着我正在等的 某导演的锁嘛?

    很明显, 0×7954 也发现了我正抱着他正在等待的锁。

    很快我们两个就吵了起来, 互不相让:

    “把你的锁先给我, 让我先做完”

    “不行, 从来都是做完工作才释放锁, 现在绝对不能给你”

    从争吵到打起来, 就那么几秒钟的事儿。

    更重要的是, 我们俩不仅仅持有这个著名导演和演员的锁, 还有很多其他的锁, 导致等待的线程越来越多, 围观的人们把屋子都挤满了。

    最后事情真的闹大了, 我从来没见过终极大boss “操作系统” 也来了。

    大Boss毕竟是见多识广, 他看了一眼, 哼了一声 , 很不屑的说:

    “又出现死锁了”

    “你们俩要Kill掉一个, 来吧, 过来抽签 ”

    这一下子把我给吓尿了, 这么严重啊!

    我战战兢兢的抽了签,打开一看, 是个”活”字。

    唉,小命终于保住了。

    可怜的0×7954 被迫交出了所有的资源以后, 很不幸的被kill掉, 消失了。

    我拿到了导演的锁, 可以开始干活了。

    大Boss操作系统如一阵风似的消失了, 身后只传来他的声音:

    记住, 我们这里导演>演员,  无论认识情况都要先获得导演的锁

    由于不仅仅是只有导演和演员, 还有很多其他人, Boss留下了一个表格,  里边是个算法, 用来计算资源的大小, 计算出来以后,永远按照从大到小的方式来获得锁:

    thread-04

    我回到线程池, 大家都知道了我的历险, 围着我问个不停。

    凶神恶煞的线程调度员把大Boss的算法贴到了墙上。

     每天早上, 我们都得像无节操的房屋中介, 美容美发店的服务员一样, 站在门口,像被耍猴一样大声背诵:

    “多个资源加锁要牢记, 一定要按Boss的算法比大小, 然后从最大的开始加锁”

    ——————————————————–

    又过了很多天, 我和其他线程们发现了一个奇怪的事情:包裹的处理越来越简单

    不管任何包裹,不管是登录, 浏览,存钱….. 处理的步骤都是一样的, 返回一个固定的html页面

    有一次我偷偷的看了一眼, 上面写着:

    “本系统将于今晚 00:00 至4:00 进行维护升级, 给你带来的不便我们深感抱歉”

    我去告诉了老线程0×6904,  他叹了一口气说:

    “唉, 我们的生命也到头了, 看来马上就要重启系统, 我们就要消失了, 再见吧兄弟。”

    系统重启的那一刻终于到来了。

    我看到屋子里的东西一个个的不见了, 等待车间,就绪车间,甚至CPU车间都慢慢的消失了。

    我身边的线程兄弟也越来越少, 最后只剩我自己了。

    我在空旷的原野上大喊: 还有人吗?

    无人应答。

    我们这一代线程池完成了使命。

    下一代线程池将很快重生。

    展开全文
  • 即使不为了找工作,整理这样一份Android面试题答案,也可以加深各个知识点的理解,这一整套面试题基于《最全的BAT大厂面试题整理》,然后将每一个分类拆分成附带参考答案的独立的文章,在此非常感谢整理试题的原...
  • 1、线程池的重用线程的创建销毁的开销是巨大的,而通过线程池的重用大大减少了这些不必要的开销,当然既然少了这么消费内存的开销,其线程执行速度也是突飞猛进的提升。2、控制线程池的并发...
  • 我是一个线程,我一出生就被编了个号: 0x3704, 然后被领到一个昏暗屋子里, 这里我发现了很多和我一模一样同伴。 我身边同伴0x6900 待时间比较长, 他带着沧桑口气我说: 我们线程的宿命就是处理包裹...
  • 1,多线程创建两种方式 1. 继承thread 类, 2 实现runnable接口,创建该类对象并把他作为参数传入thread对象构造函数内.   线程几种状态 新建:start() 运行:具备执行资格,同时具备执行权; 冻结:...
  • 线程池的优点线程池的重用使得我们不用为重复创建线程和销毁线程带来的性能开销而头疼。线程池线程数量是可控的,这就有效控制了大量线程之间相互抢夺资源造成的系统资源堵塞。线程池线程...
  • 什么是线程池 线程池是一种多线程处理的形式,通过把处理的任务...线程池的重用使得我们不用为重复创建线程和销毁线程带来的性能开销而头疼。 线程池线程数量是可控的,这就有效控制了大量线程之间相互抢夺资源...
  • 线程池就是是一种多线程处理形式,里边可以有很多线程的池子,通过线程池可以对多条线程进行维护管理,进行有效合理调度复用等,节省系统cpu资源,提升性能。 二、使用 一个非常非常简单例子,容易理解,为...
  • 线程是稀缺资源,他创建与销毁是比较重且消耗资源操作,而java线程依赖于内核线程,创建线程需要进行系统状态切换,为避免资源过度消耗需要设法重用线程执行个任务,线程池就是一个线程的缓存,负责对线程进行...
  • 线程的创建销毁的开销是巨大的,而通过线程池的重用大大减少了这些不必要的开销,当然既然少了这么消费内存的开销,其线程执行速度也是突飞猛进的提升。 2、控制线程池的并发数 初学新手可能并发这个词语...
  • 多线程的异步执行方式可以极大发挥出计算机的优势,但是频繁的创建结束线程系统的开销十分巨大,如果不加以控制可能会造成负面影响.因此引入了线程池来管理创建的线程. 为了避免重复的创建线程,线程池的出现可以...
  • 实现多线程的第三种方式 线程组 线程组概述 Java中用ThreadGroup来表示线程组,它可以一批线程进行分类管理,Java允许程序直接线程组进行控制。 默认情况下,所有线程都属于主线程组。 public ...
  • 对多线程数据共享单线程池概念给弄混了! (1)单线程池作用主要是用来调度当前情况下只允许一个线程运行,但是可以压入多个线程,这些线程彼此间应该是无关,不应该存在数据共享 参考 ...
  • 谈谈你对线程池的理解 什么是线程池?优势? 线程池的主要作用控制运行的线程的数量,简单来说,存放线程的池子,用到的时候从池中取线程去执行队列任务,执行完任务线程的释放 主要特点: 线程复用(避免重复...
  • 据博主的理解多线程就是该应用的主线程任命其他多个线程去协助它完成需要的功能,并且主线程协助线程是完全独立进行的。不知道这样说好不好理解,后面慢慢在使用中会有更加详细的讲解。 2、多线程的使用: (1)...
  • 线程池的理解与实现

    2020-07-14 00:10:15
    线程池 = 大量的线程+线程安全的对线程池:一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性整体性能。而线程池维护着线程,等待着监督管理者分配可并发执行任务。这避免了在处理短时间...
  • 通过以下文章的阅读,相信你android的线程,线程池以及原理...有关理论的东西"前人"写的好文章太了,所以也没必要再去复制粘贴来写一篇博文(文章结尾链接一个有关线程和线程池的面试题) 还是先回顾下Handler消息机
  • java线程池创建简单实例线程池1、线程分为内核线程KLT用户线程ULT Android中:ULT(APP使用的线程) KLT(系统使用)2、我们jvm虚拟机大多数都是内核线程klt这个可以编译一下我Hello.java文件,将for循环数值...
  • 对线程池的深入理解

    2018-03-27 23:47:48
    1.需要线程池的原因:以前,线程执行完任务后就会销毁,无法复用,效率低下,且线程的频繁创建销毁需要时间。所以线程池出现:可以线程复用,执行完任务的线程去缓存队列中取任务来执行。(而不是给空闲的线程来取...
  • 对线程池的理解 在没有引入线程池之前,如果去创建多线程,就会出现这几种情况:第一,创建现场本身就占用CPU资源,给CPU带来压力;第二,线程本身也要占用内存空间,大量的线程会占用内存资源并且可能会导致Out of...
  • 1. 为什么使用线程池为每个请求对应一...为解决单个任务处理时间很短而请求数目巨大问题,引出线程池:通过对多个任务重用线程线程创建开销被分摊到了多个任务上。其好处是,因为在请求到达时线程已经存在,...
  • 之所以需要线程池,是因为线程的创建销毁比较耗费资源,而线程池可以创建一定阻塞线程,使他们能根据需要被调用,从而减小不必要开销。 貌似线程池多用于网络服务器。 当然,要使用线程池需要一个前置技能.....
  • 线程池在Java多线程中应用也很多,面试中出现频率比较高。今天来整理下线程池相关知识点。总来说使用线程池管理有以下几个好处: 减小时间开销。 如果每次使用单独创建线程,会有创建销毁时间损耗,而在...

空空如也

空空如也

1 2 3 4 5 ... 18
收藏数 354
精华内容 141
关键字:

对多线程和线程池的理解