Java多线程基础

本文详细介绍了Java多线程的基础知识,包括线程的生命周期、创建、优先级、常用操作如sleep、interrupt、join等,以及线程同步、死锁、守护线程和线程池的工作原理和参数配置。内容涵盖了线程同步机制如synchronized和volatile的对比,以及线程池的拒绝策略和不同类型线程池的特点和应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ref:
https://www.runoob.com/java/java-multithreading.html
https://blog.youkuaiyun.com/weixin_43914658/article/details/109449580
https://blog.youkuaiyun.com/cheidou123/article/details/95096467

基本概念

进程(Process):
进程是程序的⼀次执⾏过程,是系统运⾏程序的基本单位。系统运⾏⼀个程序即是⼀个进程从创建,运⾏到消亡的过程
在 Java 中,当我们启动 main 函数时其实就是启动了⼀个 JVM 的进程,⽽ main 函数所在的线程就是这个进程中的⼀个线程,也称主线程。

线程(Thread)与进程相似,但线程是⼀个⽐进程更⼩的执⾏单位,是程序执行的最小单位。⼀个进程在其执⾏的过程中可以产⽣多个线程。与进程不同的是同类的多个线程共享同⼀块内存空间和⼀组系统资源,所以系统在产⽣⼀个线程,或是在各个线程之间作切换⼯作时,负担要⽐进程⼩得多,也正因为如此,线程也被称为轻量级进程。

线程是进程划分成的更⼩的运⾏单位。线程和进程最⼤的不同在于基本上各进程是独⽴的,⽽各线程则不⼀定,因为同⼀进程中的线程极有可能会相互影响。线程执⾏开销⼩,但不利于资源的管理和保护;⽽进程正相反。

程序是含有指令和数据的⽂件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。

以B站看视频为例:
打开B站看视频可以理解为实现了一个“进程”,而在看的时候,会同时听到声音,看到图片,还有可以发弹幕等……这些都是多线程实现。
例子:
⼀个 Java 程序的运⾏是 main 线程和多个其他线程同时运⾏。

用途

通过对多线程的使用,可以编写出非常高效的程序。不过请注意,如果你创建太多的线程,程序执行的效率实际上是降低了,而不是提升了。上下文的切换开销也很重要,如果你创建了太多的线程,CPU 花费在上下文的切换的时间将多于执行程序的时间!
并发编程的⽬的就是为了能提⾼程序的执⾏效率提⾼程序运⾏速度,但是并发编程并不总是能提⾼程序运⾏速度的,⽽且并发编程可能会遇到很多问题,⽐如:内存泄漏、上下⽂切换、死锁还有受限于硬件和软件的资源闲置问题。

生命周期

在这里插入图片描述

新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。

死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

创建线程

1.继承Thread类,重写run方法(无返回值)
2.实现Runnable接口,重写run方法(无返回值)
3.实现Callable接口,重写call方法(有返回值且可以抛出异常)

  • 采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
  • 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。

线程优先级

优先级高的线程获得的CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务。

①线程优先级用thread.setPriority(int a)( 1<=a<=10)方法来进行赋值
②线程优先级有继承性,如果主线程启动threadA线程且threadA线程没有另外赋予优先级,则threadA线程优先级和main线程一样(即通过A线程启动线程B,线程B没有设置优先级则优先级同A一致)。线程默认优先级为5.
③CPU尽量将执行资源让给线程优先级高的,即线程优先级高的总是会大部分先执行,但是不代表高优先级的线程全部都先执行完再执行低优先级的线程
④在调用start方法前面设置

常见操作

sleep

Thread.sleep(5000); // 当前线程休眠五秒钟。在哪个线程里面调用sleep()方法就阻塞哪个线程。
作用是需要暂缓线程的执行速度,则可以让当前线程休眠,即当前线程从“运行状态”进入到“阻塞状态”。
sleep方法会指定休眠时间,线程休眠的时间会大于或等于该休眠时间,该线程会被唤醒,此时它会由“阻塞状态”变成“就绪状态”,然后等待CPU的调度执行。

interrupt

interrupt()常常被用来终止“阻塞状态”线程.并不能真正的中断线程,需要配合其它方式。

该方法将会设置该线程的中断状态位,即设置为true,中断的结果线程是终止状态、还是阻塞状态或是继续运行至下一步,就取决于该程序本身。线程会不时地检测这个中断标示位,以判断线程是否应该被中断(即中断标示值是否为true)。它并不像stop方法那样会中断一个正在运行的线程

thread1.interrupt()
thread1.isInterrupted()  //检查中断状态 
每当中断执行都会产生InterruptedException异常。
catch(InterruptedException e){} //可定义被中断后执行的操作

1、终止处于“阻塞状态”的线程
当线程由于被调用了sleep(), wait(), join()等方法而进入阻塞状态;若此时调用线程的interrupt()将线程的中断标记设为true。由于处于阻塞状态,中断标记会被清除,同时产生一个InterruptedException异常。将InterruptedException放在适当的为止就能终止线程
2、终止处于“运行状态”的线程
interrupt()并不会终止处于“运行状态”的线程!它会将线程的中断标记设为true。thread1.isInterrupted() //检查中断状态
参考:interrupt解析

join

join可以使得线程之间的并行执行变为串行执行。
join里面可以传等待时间,0的话表示等待执行完毕。
thread1.join()方法只会使主线程(或者说调用t.join()的线程)进入等待池并等待thread1线程执行完毕后才会被唤醒。不影响其他线程。

// 主线程
public class Father extends Thread {
    public void run() {
        Son s = new Son();
        s.start();
        s.join();
        ...
    }
}
// 子线程
public class Son extends Thread {
    public void run() {
        ...
    }
}

上面的有两个类Father(主线程类)和Son(子线程类)。因为Son是在Father中创建并启动的,所以,Father是主线程类,Son是子线程类。
在Father主线程中,通过new Son()新建“子线程s”。接着通过s.start()启动“子线程s”,并且调用s.join()。在调用s.join()之后,Father主线程会一直等待,直到“子线程s”运行完毕;在“子线程s”运行完毕之后,Father主线程才能接着运行。
参考:join用法

yeild

使当前线程从执行状态(运行状态)变为可执行态(就绪状态),将当前的资源暂时让步出去。cpu会从众多的可执行态里选择,也就是说,当前也就是刚刚的那个线程还是有可能会被再次执行到的。
Thread.yield(); // 当前线程让步出去

wait/notify/notifyAll

wait与notify是java同步机制中重要的组成部分。结合与synchronized关键字使用,可以建立很多优秀的同步模型。
三者都需要在同步方法或者同步代码块调用。否则抛java.lang.IllegalMonitorStateException运行异常

wait()
调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁,或者叫管程)
当前线程释放锁。使得当前线程等待,直到另一个线程在这个对象上调用了notify()方法或者notifyAll()方法。

notify
调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程。有多个线程等待时,它不能保证哪个线程会被唤醒,这取决于线程调度器。

notifyAll
唤醒所有正在等待该对象的monitor的线程,然后竞争获取锁。但哪一个将首先处理取决于操作系统的实现。

例如:
  synchronized(obj) {
  while(!condition) {
  obj.wait();
  }
  obj.doSomething();
  }
  
  当线程A获得了obj锁后,发现条件condition不满足,无法继续下一处理,于是线程A就wait()。
  在另一线程B中,如果B更改了某些条件,使得线程A的condition条件满足了,就可以唤醒线程A :
  
  synchronized(obj) {
  condition = true;
  obj.notify();
  }

wait和notify方法均可释放对象的锁,但wait同时释放CPU控制权,即它后面的代码停止执行,线程进入阻塞状态,而notify方法不立刻释放CPU控制权,而是在相应的synchronized(){}语句块执行结束,再自动释放锁。#例子: 当B调用obj.notify/notifyAll的时候,B正持有obj锁,因此,A1,A2,A3虽被唤醒,但是仍无法获得obj锁。直到B退出synchronized块,释放obj锁后,A1,A2,A3中的一个才有机会获得锁继续执行。
wait(),notify(),notifyAll()不属于Thread类,而是属于Object基础类,

参考:123

sleep 和 wait 的区别

两者都可以暂停线程的执⾏。
最主要的区别在于:

  • sleep ⽅法没有释放锁,⽽ wait ⽅法释放了锁。
  • Wait 通常被⽤于线程间交互/通信,sleep 通常被⽤于暂停执⾏。
  • wait() ⽅法被调⽤后,线程不会⾃动苏醒,需要别的线程调⽤同⼀个对象上的 notify() 或者notifyAll() ⽅法。sleep() ⽅法执⾏完成后,线程会⾃动苏醒。或者可以使⽤ wait(longtimeout)超时后线程会⾃动苏醒。

线程同步

为什么?

比如每个线程都改变同一个数字,则结果会不准确,这是数据共享问题。
解决数据共享问题必须使用同步。

是什么?

所谓的同步就是指多个线程在同一个时间段内只能有一个线程执行指定的代码,其他线程要等待此线程完成之后才可以继续进行执行

怎么实现?

主要了解下synchronized。
synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的⽅法或者代码块在任意时刻只能有⼀个线程执⾏。

锁的概念

java的内置锁:每个java对象都可以有一个用于实现同步功能的锁,这些锁成为内置锁。
线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁。

java的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的。
对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。
但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的

synchronized

实例锁级别
1、用在普通方法。此时,同一个实例只有一个线程能获取锁,进入这个方法。

private synchronized void synchronizedMethod() {
    System.out.println("synchronizedMethod");
}

2、同步this实例

private void synchronizedThis() {
    synchronized (this) {
    }
}

3、同步对象实例
只有获取到这个 LOCK 实例的锁才能进入这个方法

private void synchronizedInstance() {
    synchronized (LOCK) {
        System.out.println("synchronizedInstance");
    }
}

对象锁级别
1、同步静态方法。
不管你有多少个类实例,同时只有一个线程能获取锁进入这个方法

private synchronized static void synchronizedStaticMethod() {
    System.out.println("synchronizedStaticMethod");
}

2、同步类
不管你有多少个类实例,同时只有一个线程能获取锁进入这个方法

private void synchronizedClass() {
    synchronized (TestSynchronized.class) {
        System.out.println("synchronizedClass");
    }
}
private void synchronizedGetClass() {
    synchronized (this.getClass()) {
        System.out.println("synchronizedGetClass");
    }
}

类锁与实例锁不相互阻塞,但相同的类锁,相同的当前实例锁,相同的对象锁会相互阻塞。
所以如果⼀个线程A调⽤⼀个实例对象的⾮静态 synchronized ⽅法,⽽线程B需要调⽤这个实例对象所属类的静态 synchronized ⽅法,是允许的,不会发⽣互斥现象,因为访问静态synchronized ⽅法占⽤的锁是当前类的锁,⽽访问⾮静态 synchronized ⽅法占⽤的锁是当前实例对象锁。

举例:
一个object就像一个大房子,大门永远打开。房子里有很多房间(也就是方法)。 这些房间有上锁的(synchronized方法),
和不上锁之分(普通方法)。房门口放着一把钥匙(key),这把钥匙可以打开所有上锁的房间。
所有想调用该对象方法的线程比喻成想进入这房子某个房间的人。

一个人想进入某间上了锁的房间,他来到房子门口,看见钥匙在那儿(说明暂时还没有其他人要使用上锁的
房间)。于是他走上去拿到了钥匙,并且按照自己的计划使用那些房间。注意一点,他每次使用完一次上锁的房间后会马上把钥匙还回去。即使他要连续使用两间上锁的房间,中间他也要把钥匙还回去,再取回来。

这时其他人可以不受限制的使用那些不上锁的房间,一个人用一间可以,两个人用一间也可以,没限制。但是如果当某个人想要进入上锁的房间,他就要跑到大门口去看看了。有钥匙当然拿了就走,没有的话,就只能等了。

参考:
synchronized5种使用
synchronized 售票例子

synchronized 和 volatile

原子性即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

当前的 Java 内存模型下,线程可以把变量保存本地内存(⽐如机器的寄存器)中,⽽不是直接在主存中进⾏读写。这就可能造成⼀个线程在主存中修改了⼀个变量的值,⽽另外⼀个线程还继续使⽤它在寄存器中的变量值的拷⻉,造成数据的不⼀致
在这里插入图片描述

1、volatile保证了共享变量的可见性,被Volatile修饰的变量如果值发生了变化,其他线程立刻可见,避免出现脏读现象。 synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
2、volatile关键字是线程同步的轻量级实现,所以volatile性能肯定⽐synchronized关键字要好
3、volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
4、volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
5、volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。

使用volatile关键字仅能实现对原始变量(如boolen、 short 、int 、long等)操作的原子性,但需要特别注意,
volatile不能保证复合操作的原子性。 即使只是i++,实际上也是由多个原子操作组成:read i; inc; write
i,假如多个线程同时执行i++,volatile只能保证他们操作的i是同一块内存,但依然可能出现写入脏数据的情况。

版权声明:本文为优快云博主「Heaven-Wang」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/suifeng3051/article/details/52611233

线程死锁

所谓死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象。过多的同步就有可能出现死锁。

例如:
线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对⽅的资源,所以这两个线程就会互相等待⽽进⼊死锁状态

如何避免线程死锁?

  1. 破坏互斥条件:这个条件我们没有办法破坏,因为我们⽤锁本来就是想让他们互斥的(临界资源需要互斥访问)。
  2. 破坏请求与保持条件:⼀次性申请所有的资源。
  3. 破坏不剥夺条件:占⽤部分资源的线程进⼀步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
  4. 破坏循环等待条件:靠按序申请资源来预防。按某⼀顺序申请资源,释放资源则反序释放。破坏循环等待条件。

守护线程

Java中的线程分为两类,用户线程和守护线程。守护线程(Daemon)是一种运行在后台的线程服务线程,当用户线程存在时,守护线程可以同时存在,但是,当用户线程不存在时,守护线程会全部消失。
守护线程最典型的应用就是 GC (垃圾回收器)。

线程池

为什么要⽤线程池?

池化技术的思想主要是为了减少每次获取资源的消耗,提⾼对资源的利⽤率。
创建线程和销毁线程的花销是比较大的,因为线程其实也是一个对象,创建一个对象,需要经过类加载过程,销毁一个对象,需要走GC垃圾回收流程,都是需要资源开销的。

线程池可以把创建和销毁的线程的过程去掉。
在任务执行前,需要从线程池中拿出线程来执行。
在任务执行完成之后,需要把线程放回线程池。

使⽤线程池的好处:

  • 降低资源消耗。通过重复利⽤已创建的线程降低线程创建和销毁造成的消耗。
  • 提⾼响应速度。当任务到达时,不需要等新线程创建。
  • 方便管理线程。使⽤线程池可以进⾏统⼀的分配,调优和监控。

线程池中的几种重要的参数

corePoolSize
线程池中的核心线程数量,核心线程在没有用的时候,也不会被回收

maximumPoolSize
线程池中可以容纳的最大线程的数量

keepAliveTime
线程池中除了核心线程之外的其他的最长可以保留的时间
在线程池中,除了核心线程即使在无任务的情况下也不能被清除,其余的都是有存活时间的,意思就是非核心线程可以保留的最长的空闲时间,

util
计算时间的一个单位。

workQueue
等待队列,任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出)。

threadFactory
创建线程的线程工厂。可以给创建的线程设置有意义的名字,可方便排查问题。

handler
是一种拒绝策略,我们可以在任务满了之后,拒绝执行某些任务。

线程池可以通过ThreadPoolExecutor来创建,构造函数如下:

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

线程池执行流程在这里插入图片描述

1、提交一个任务,线程池里存活的核心线程数小于线程数corePoolSize时,线程池会创建一个核心线程去处理提交的任务。
2、如果线程池核心线程数已满,即线程数已经等于corePoolSize,一个新提交的任务,会被放进任务队列workQueue排队等待执行。如果核心线程执行完任务,去队列取任务,继续执行。
3、当线程池里面存活的线程数已经等于corePoolSize了,并且任务队列workQueue也满,判断线程数是否达到maximumPoolSize,即最大线程数是否已满,如果没到达,创建一个非核心线程执行提交的任务。非核心线程无任务执行时,等待keepAliveTime 时间后销毁。
4、如果当前的线程数达到了maximumPoolSize,还有新的任务过来的话,直接采用拒绝策略处理。

拒绝策略

当请求任务不断的过来,而系统此时又处理不过来的时候,我们需要采取的策略是拒绝服务。
在ThreadPoolExecutor中已经包含四种处理策略
AbortPolicy
(抛出一个异常,默认的)
DiscardPolicy
(直接丢弃任务)
DiscardOldestPolicy
(丢弃队列里最老的任务,将当前这个任务继续提交给线程池)
CallerRunsPolicy
(交给线程池调用所在的线程进行处理)
可以自定义拒绝策略,实现RejectedExecutionHandler接口即可。

线程池有哪几种工作队列?

1、ArrayBlockingQueue(有界队列)
是一个用数组实现的有界阻塞队列,按FIFO排序量。
2、LinkedBlockingQueue(可设置容量队列)
基于链表结构的阻塞队列,按FIFO排序任务,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE,吞吐量通常要高于ArrayBlockingQuene;newFixedThreadPool线程池使用了这个队列
3、DelayQueue(延迟队列)
是一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序。newScheduledThreadPool线程池使用了这个队列。
4、PriorityBlockingQueue(优先级队列)
是具有优先级的无界阻塞队列;
5、SynchronousQueue(同步队列)
一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene,newCachedThreadPool线程池使用了这个队列。

几种线程池的特点和使用场景

newSingleThreadExecutor:
一个单线程的线程池,可以用于需要保证顺序执行的场景,并且只有一个线程在执行。

  • 核心线程数为1
  • 最大线程数也为1
  • 阻塞队列是LinkedBlockingQueue
  • keepAliveTime为0

newFixedThreadPool:
一个固定大小的线程池,可以用于已知并发压力的情况下,对线程数做限制。

  • 核心线程数和最大线程数大小一样
  • 没有所谓的非空闲时间,即keepAliveTime为0
  • 阻塞队列为无界队列LinkedBlockingQueue

newCachedThreadPool:
一个可以无限扩大的线程池,用于并发执行大量短期的小任务。当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程,当有任务来时,又智能的添加新线程来执行。

  • 核心线程数为0
  • 最大线程数为Integer.MAX_VALUE
  • 阻塞队列是SynchronousQueue
  • 非核心线程空闲存活时间为60秒

newScheduledThreadPool:
可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景。

  • 最大线程数为Integer.MAX_VALUE
  • 阻塞队列是DelayedWorkQueue
  • keepAliveTime为0
  • scheduleAtFixedRate() :按某种速率周期执行
  • scheduleWithFixedDelay():在某个延迟后执行

面试题:使用无界队列的线程池会导致内存飙升吗?
答案 :会的,newFixedThreadPool使用了无界的阻塞队列LinkedBlockingQueue,如果线程获取一个任务后,任务的执行时间比较长(比如,上面demo设置了10秒),会导致队列的任务越积越多,导致机器内存使用不停飙升, 最终导致OOM。

《阿⾥巴巴Java开发⼿册》中强制线程池不允许使⽤ Executors 去创建,⽽是通过ThreadPoolExecutor
的⽅式,这样的处理⽅式让写的同学更加明确线程池的运⾏规则,规避资源耗尽的⻛险Executors

返回线程池对象的弊端如下:
FixedThreadPool 和 SingleThreadExecutor:允许请求的队列⻓度为 Integer.MAX_VALUE,可能堆积⼤量的请求,从⽽导致OOM。
CachedThreadPool 和 ScheduledThreadPool:允许创建的线程数量为 Integer.MAX_VALUE,可能会创建⼤量线程,从⽽导致OOM

线程池各个状态

在这里插入图片描述

RUNNING

该状态的线程池会接收新任务,并处理阻塞队列中的任务; 调用线程池的shutdown()方法,可以切换到SHUTDOWN状态;
调用线程池的shutdownNow()方法,可以切换到STOP状态; SHUTDOWN

该状态的线程池不会接收新任务,但会处理阻塞队列中的任务; 队列为空,并且线程池中执行的任务也为空,进入TIDYING状态; STOP

该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行的任务; 线程池中执行的任务为空,进入TIDYING状态;
TIDYING

该状态表明所有的任务已经运行终止,记录的任务数量为0。 terminated()执行完毕,进入TERMINATED状态

TERMINATED

该状态表示线程池彻底终止

执⾏execute()⽅法和submit()⽅法的区别是什么呢?

1、 execute()⽅法⽤于提交不需要返回值的任务,所以⽆法判断任务是否被线程池执⾏成功与否;
2、 submit()⽅法⽤于提交需要返回值的任务。线程池会返回⼀个Future类型的对象,通过这个Future对象可以判断任务是否执⾏成功

原文链接:https://blog.youkuaiyun.com/qq_29373285/article/details/85238728
https://zhuanlan.zhihu.com/p/73990200

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值