多线程:
windows操作系统是多任务操作系统,它以进程为单位,一个进程是一个包含有自身地址的程序,每个正在独立执行的程序都称为进程,进程是系统进行资源分配和调用的独立单位,每个进程有自己的内存空间和系统资源。系统可以分配给每个进程一段有限的使用CPU时间,CPU在这个时间片中执行某个进程,然后下一个时间片又跳到另一个进程中去执行,由于CPU转换较快,所以使的每个进程好像同时进行,多核CPU的多核同时运行,但是进程数远远大于核数,所以还是要依靠切换进程来做。一个线程则是进程中的执行流程,一个进程中可以同时包括多个线程,每个线程也可以得到一小段程序的执行时间,这样一个进程就可以具有多个并发执行的线程,在单线程中,程序的代码按照调用顺序依次往下执行,如果需要一个进程同时完成多段代码的操作,就需要产生多线程(多条执行路径)。多线程可以提高应用程序的使用率,为了有更高的几率抢到CPU的执行权,但我们不能保证哪一个线程能够在哪一个时刻抢到,所以线程的执行有随机性。在并发编程中,我们通常会遇到以下三个问题:原子性问题,可见性问题,有序性问题。
线程的两种调度模型:平均分配每个线程占用CPU的时间片,上面说的就是这种,第二种就是抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,会随机选一个,优先级高仅仅表示线程获取的cpu时间片的几率相对对多些,也就是说不是高的执行完再执行低的,需要多多次运行才能看到效果。java用的就是抢占式。
优先级:Java中每个线程都有优先级,默认情况下,新建线程的优先级与创建该线程的优先级相同,每当线程调度器选择要运行的线程时,通常选择优先级较高的线程,设置线程的优先级线程对象。setpriority(int 值)值在Thread。MIN_PRIORITY和Thread。MAX_PRIORITY之间, 一般默认为Thread。NORM_PRIORITY ,如果不在1-10之间产生一个IllegalArgumentException异常。
Java多线程实现方式主要有四种:其中前两种方式线程执行完后都没有返回值,后两种是带返回值的,也可以用匿名内部类的方式实现多线程。
1.继承Thread类:Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法会调用一个native方法start0,它将启动一个新线程,并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。但是如果不通过start()启动线程,直接调用run方法,那么跟直接调用一个普通方法没有区别。如果start()方法调用一个已经启动的线程,系统将抛出java.lang.IllegalThreadStateException异常。主方法线程启动由java虚拟机负责,我们自己启动自己的线程。Thread里的currentThread可以返回当前正在执行的线程对象。
2.实现Runnable接口:如果自己的类已经extends另一个类,就无法直接extends Thread,此时,可以实现一个Runnable接口。另一种情况是它适用于多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效的分离,换句话说就是只需要创建一个Runnable对象,可以通过这个Runnable对象创建多个线程对象,并且多个线程对象公用一个Runnable对象数据。分为三步:1.建立Runnable对象(一个自定义的类继承了Runnable接口,并实现了run()方法)。 2.使用参数为Runnable 对象的构造方法创建 Thread对象,3.用Thread对象调用start方法启动线程。其实上面的Thread类就是实现了Runnable接口,其中run()方法正是对Runnable接口中run()方法的具体实现。
3.实现Callable接口通过FutureTask包装器来创建Thread线程,一个泛型接口,这个泛型其实就是这个接口的Call方法的返回值类型。这个call方法相当于上面run方法,而区别就是这个有泛型的返回值。Future 表示异步计算的结果,Future 的get方法的返回值为可以返回泛型V,但是如果get的时候任务还没有执行完,就会会一直等到任务完成,形成了阻塞。在get的时候可以设置时间,如果超出时间为获得结果会抛出异常,java.util.concurrent.TimeoutException。cancel(boolean mayInterruptIfRunning)
:取消子任务的执行,如果这个子任务已经执行结束,或者已经被取消,或者不能被取消,这个方法就会执行失败并返回false
;如果子任务还没有开始执行,那么子任务会被取消,不会再被执行;如果子任务已经开始执行了,但是还没有执行结束,根据mayInterruptIfRunning
的值,如果mayInterruptIfRunning = true
,那么会中断执行任务的线程,然后返回true,如果参数为false,会返回true,不会中断执行任务的线程。isCancelled()
,判断任务是否被取消,如果任务执行结束(正常执行结束和发生异常结束,都算执行结束)前被取消,也就是调用了cancel()
方法,并且cancel()
返回true,则该方法返回true,否则返回false.所以cancel(true)
方法,只是调用t.interrupt()
,此时,如果t
因为sleep(),wait()
等方法进入阻塞状态,那么阻塞的地方会抛出InterruptedException
;如果线程正常运行,需要结合Thread
的interrupted()
方法进行判断,才能结束,否则,cancel(true)
不能结束正在执行的任务。
这也就可以解释前面我遇到的问题,有的情况下,使用 futuretask.cancel(true)方法并不能真正的结束子任务执行。
4.使用ExecutorService、Callable、Future实现有返回结果的多线程。可返回值的任务必须实现Callable接口。类似的,无返回值的任务必须实现Runnable接口。submit的时候其实就是执行了一个FutureTask的子类对象。如果超过线程数量就等待其他完成。
线程休眠:在指定的毫秒数内当前执行的线程暂停执行,Thread.Sleep(毫秒)。
sleep与wait区别:调用wait方法后可以使该线程从运行状态进入就绪状态,而sleep方法也可以达到 这个效果,区别在于调用sleep方法后线程不释放锁,但是用wait()方法的线程释放锁。wait(time)这种形式与sleep都是指在此时间内暂停,wait()方法永久等待下去直到notify或者notifyall,sleep的线程睡眠到期自动苏醒 ,并返回到就绪状态,不是运行状态。sleep和wait后在哪里沉睡就在那里唤醒,sleep()是静态方法,只能控制当前正在运行的线程。
wait、notify、notifyAll 是object中的方法,notify唤醒任意一个wait后等待的线程,notifyAll是唤醒所有等待的线程。
wait和await的区别,wait在object中,await在condition中。两者在使用上也是非常类似,都需要先获取某个锁之后才能调用,而不同的是 Object wait,notify 对应的是 synchronized 方式的锁,Condition await,singal 则对应的是 ReentrantLock (实现 Lock 接口的锁对象)对应的锁。但这里面有一个最大的问题就是 synchronized 方式对应的 wait, notify 不能有多个谓词条件,Lock 对应的 Condition await, signal 则可以有多个谓词条件。没有多个谓词条件带来的问题在于,例如队列已满,所有的生产者现场阻塞,某个时刻消费者消费了一个元素,则需要唤醒某个生产者线程,而通过 Object notify 方式唤醒的线程不能确保一定就是一个生产者线程,因为 notify 是随机唤醒某一个正在该 synchronized 对应的锁上面通过 wait 方式阻塞的线程,如果这时正好还有消费者线程也在阻塞中,则很可能唤醒的是一个消费者线程.与之不同的 Condition await, signal 方式则可以对应多个谓词条件(notEmpty, notFull),可以很方便的实现让生产者线程和消费者线程分别在不同的谓词条件上进行等待。
线程的加入:如果当前程序为多线程时,假如存在一个线程A,现在需要插入线程B,并要求线程B先执行完毕,然后在继续执行线程A,此时可以使用Tread类中的Join方法来来完成,当某个线程使用join()方法加入到另一个线程时,另一个线程会等待该线程执行完毕再继续执行。
thread类中提供了一种礼让方法,使用yield()方法表示,它只是给当前正处于运行状态下的线程一个提醒,告知它可以将资源礼让给其他线程,但这仅仅使一种暗示,没有任何一种机制保证当前线程会将资源礼让。
后台线程:setDaemon()方法可以把该线程标记为守护线程或用户线程,当正在运行的线程都是守护线程时,java虚拟机退出,该方法必须在启动该线程前调用,可以实现主线程结束,其他线程停止需求。
中断线程: thread对象调用interrupt(),通知线程应该中断了,注意:调用interrupt()方法并不会使得线程中断,而是使得线程的中断标志置为true,需要被调用的线程配合中断,在正常运行任务时,经常检查本线程的中断标志,如果被设置了中断标志就自行停止线程.调用后会有两种情况:
1.如果线程处于被阻塞状态,那么该线程将立即退出被阻塞状态,并且抛出一个InterrupedException异常.
2.如果线程初一正常活动状态,那么会将该线程的中断标志设置为true.被设置中断标志的线程将继续中场运行,不受影响.
stop、suspend、resume被抛弃的原因:
stop方法会立即释放所有他锁住对象上的锁。这会导致对象处于不一致的状态。假如一个方法在将钱从一个账户转移到另一个账户的过程中,在取款之后存款之前就停止了。那么现在银行对象就被破坏了。因为锁已经被释放了。当线程想终止另一个线程的时候,它无法知道何时调用stop是安全的,何时会导致对象被破坏。所以这个方法被弃用了。suspend()方法就是将一个线程挂起(暂停),resume()方法就是将一个挂起线程复活继续执行。suspend方法不会破坏对象,但是调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁,没任何线程可以使用这个锁住的资源直到suspend的目标线程被resumed,如果一条线程将去resume目标线程之前尝试持有这个重要的系统资源再去resume目标线程,这两条线程就相互死锁了。
此 时,其他任何线程都不能访问锁定的资源,除非被"挂起"的线程恢复运行。对任何线程来说,如果它们试图使用任何一个锁定的资源,又想恢复目标线程就会造成死锁。
线程可以有6种状态, New(新创建)Runnable(可运行状态:调用start后)Blocked(被阻塞)Waiting(等待)Timed waiting(计时等待)Terminated(被终止)要确定一个线程的状态可以调用getState方法。
因为cpu的一次操作是原子性的,所以有可能出现多个窗口卖电影票案例中相同的票被卖了多次情况。由于随机和延迟又导致了出现负票,所以引入了线程安全机制。出现问题的根本原因:1.多线程环境,2.共享数据,3.多条语句操作操作共享数据(非原子性)。
线程同步机制;多线程程序,会发生线程抢占资源问题,所以java提供了线程同步机制来防止资源冲突。例如火车票购买系统。解决方法就是给定时间内只允许一个线程访问共享资源,这时需要给资源上一道锁,就好比一个人上洗手间,一个人进去之后将门上锁,等他出来再将锁打开,然后其他人才可以进入。
1.同步块:Java的同步机制使用 synchronized关键字。用synchronized创建同步快 这个同步快也称临界区,语法格式synchronized(Object){...}.将共享资源的操作放置在synchronized定义区域内,这样当其他其他线程获取到这个锁时,必须等待锁被释放时才能进入该区域,同步可以解决安全问题的根本原因就在那个对象上,该对象如同锁的功能,Object为任意一个非null对象,每个对象都有一个标志位,如果为0状态,表明此同步块中存在其他线程在运行,这时线程处于就绪状态,直到处于同步快中的线程执行完同步快中的代码为止,这时该对象的标识被设置为1.该线程才能执行同步块中代码,并将object对象设置为0,防止其他线程执行同步快中代码,因此要多个线程用同一把锁,所以要把Object代表的任意对象用同一个非null对象。弊端:当线程相当多时,因为每个线程都会去判断同步的锁,很耗费资源,降低了程序运行效率。这里需要注意的是,synchronized后面括号里的一定要是引用类型。比如可以是Integer 类型,但不能是int。很简单,基本类型都是传值使用当然无法满足需要了。有兴趣的可以试下 int,发现会有编译错:int is not a valid type's argument for the synchronized statemen
2.同步方法:同步方法就是在方法前面修饰synchronized关键字的方法,语法格式synchronized void f(){}。当某个对象调用了同步方法时,该对象上的其他同步方法必须等待该同步方法执行完毕才能被执行,必须将每个能访问共享资源的方法修饰为synchronized。否则会出错。同步方法锁的对象是this。静态方法锁的对象是类的字节码文件(类名.class返回的对象)。如果同步块中调用同步方法, 那么两者的锁要一致。
同步的问题:1.效率低 2.如果出现了同步嵌套,就容易产生死锁问题,项目可以统一规定大锁包小锁,比如帮派锁包玩家锁。
同步过程中的死锁问题:指两个或两个以上的线程在执行过程中,因争夺资源产生的一种互相等待的现象,换句话说就是我需要你的数据,你需要我的数据。
3.lock:Jdk5以后出现的新的锁对象。lock()方法获取锁(在执行需要同步的代码前调用),unlock()方法释放锁(在执行完需要同步的代码后调用)。ReentrantLock是lock的实现类,是一种可重入锁。最好配合try。。finally 使用, 释放放在finally里调用。适用在实现runnable时共用一个对象创建多线程时,在实现类里定义一个lock字段即可。Lock obj = new ReentrantLock()obj .lock() obj .unlock()。
synchronized 和lock 的区别:
1.首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
2.synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
3.synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
4.用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
5.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平可非公平(两者皆可)
如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断,
如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情。
6.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
Condition 控制线程通信 ,Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版本中的不同。在 Condition 对象中,与 wait、notify 和 notifyAll 方法对应的分别是await、signal 和 signalAll。Condition对象是需要关联Lock对象的,也就是说Condition的使用是需要依赖Lock对象的。要为特定 Lock 实例获得Condition 实例,请使用其 newCondition() 方法。Lock和Condition结合应用以实现线程按序交替。await、signal ,signalAll在调用前要获取对应的lock锁。经典案例就是有界队列。
并行和并发:前者是逻辑上同时发生,旨在某一个时间内同时运行多个程序,后者是物理上同时发生,指在某一个时间点同时运行多个程序。
同步:行一个操作之后,等待结果,然后才继续执行后续的操作。
异步:执行一个操作后,可以去执行其他的操作,然后等待通知再回来执行刚才没执行完的操作。
阻塞:进程/线程给CPU传达一个任务之后,一直等待CPU处理完成,然后才执行后面的操作。
非阻塞:进程/线程给CPU传达任我后,继续处理后续的操作,隔断时间再来询问之前的操作是否完成。这样的过程其实也叫轮询。
经典例子:
1.老张把水壶放到火上,立等水开(同步阻塞).
2.老张把水壶放到火上,去客厅看电视,时不时去看水开了没有(同步非阻塞)。
有一个响水壶,水开了后,能通知老张。
3.老张把响水壶放到火上,立等水开。(异步阻塞)
4.老张把响水壶放在火上,去客厅看电视,水壶响了之后通知老张去拿水壶(异步非阻塞)。
所谓同步异步,只是对水壶而言,普通水壶是同步的。响水壶是异步的,但响水壶会等自己完成主动通知老张,同步只能让调用者去轮询自己,造成效率低下,所谓阻塞非阻塞,仅仅对于老张而言,立等的老张,阻塞。看电视的老张,非阻塞。一般异步与非阻塞配合使用,否则没意义。
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。一个很经典的例子就是银行账户转账问题,比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。
可见性:可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性:即程序执行的顺序按照代码的先后顺序执行。
Java JUC简介
在 Java 5.0 提供了 java.util.concurrent (简称JUC )包,在此包中增加了在并发编程中很常用的实用工具类,用于定义类似于线程的自定义子系统,包括线程池、异步 IO 和轻量级任务框架。提供可调的、灵活的线程池。还提供了设计用于多线程上下文中的 Collection 实现等.
volatile关键字
volatile:是一个类型修饰符。volatile 的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略。尽量不用(与大多数场景冲突)
保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(实现可见性)
禁止进行指令重排序。(实现有序性)
volatile 只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性。
应用场景:
对变量的写操作不依赖于当前值。 (i++),例如 i++; 这个操作,它不是一个原子性操作,在实际执行时需要三步操作“读-改-写”:int temp = i;temp = temp + 1;i = temp;
该变量没有包含在具有其他变量的不变式中。
大多数编程情形都会与这两个条件的其中之一冲突,使得volatile变量不能像synchronized那样普遍用于实现线程安全。比如下面的例子中如果凑巧两个线程在同一时间使用不一致的值执行setlover和setupper的话,则会使范围处于不一致的状态,例如如果初始状态是(0,5)同一时间内,线程A调用setlover(4),并且线程B调用setUpper(3),显然这两个操作交叉存入的值不符合条件的,那么两个线程都会通过用于保护不变式的检查,使得最后的范围值是(4,3),最后获得一个无效的值。
乐观锁与悲观锁:两者并不是特指某个锁,而是在并发情况下保证数据完整性的不同策略。悲观锁指的就是我们平常使用的加锁机制,它假设我们总是处于最坏的情况下,如果不加锁数据完整性就会被破坏。而乐观锁指是一种基于冲突检测的方法,检测到冲突时操作就会失败。synchronized
和ReentrantLock
等独占锁就是悲观锁思想的实现,java.util.concurrent.atomic
包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
公平锁:表示线程获取锁的顺序是按照加锁的顺序来分配的,及先来先得,先进先出的顺序。
非公平锁:表示获取锁的抢占机制,是随机获取锁的,和公平锁不一样的就是先来的不一定能拿到锁。ReentrantLock等锁在构建的时候可以传入boolean 参数来构建两种锁。
CAS算法
CAS(Compare And Swap)是一种常见的“乐观锁,大部分的CPU都有对应的汇编指令,在并发数不是特别高的情况下,使用CAS的乐观锁在性能上要优于使用加锁方式的悲观锁,因为大部分情况下经过数次轮询后CAS操作都可以成功,而使用加锁机制则会造成线程的阻塞与调度,相对而言更耗时。Unsafe,是CAS的核心类,java不能直接访问操作系统底层,而是通过本地方法来访问。Unsafe类提供了硬件级别的原子操作。下面模拟了CAS的例子。
CAS 是一种无锁的非阻塞算法的实现。
CAS 包含了 3 个操作数:需要读写的内存值 V、进行比较的值 A、拟写入的新值 B
当且仅当 V 的值等于 A 时,CAS 通过原子方式用新值 B 来更新 V 的值,否则不会执行任何操作。
java.util.concurrent.atomic包下的AtomicInteger、AtomicReference等类,它们提供了基于CAS的读写操作和并发环境下的内存可见性。
AtomicInteger:保证了原子性。但是性能下降。核心方法:boolean compareAndSet(expectedValue, updateValue),AtomicBoolean、AtomicInteger、AtomicLong 和 AtomicReference 的实例各自提供对相应类型单个变量的访问和更新。每个类也为该类型提供适当的实用工具方法。AtomicIntegerArray、AtomicLongArray 和 AtomicReferenceArray 这些类在为其数组元素提供原子更新。
CountDownLatch 闭锁
CountDownLatch 不可序列化,一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待,用给定的计数 初始化 CountDownLatch,countDown() 每被调用一次,这一数量就减一所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程 ,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。闭锁可以延迟线程的进度直到其到达终止状态,闭锁可以用来确保某些活动直到其他活动都完成才继续执行,可以确保某个计算在其需要的所有资源都被初始化之后才继续执行;也可以确保某个服务在其依赖的所有其他服务都已经启动之后才启动;也可以等待直到某个操作所有参与者都准备就绪再继续执行。
ConcurrentHashMap 锁分段机制
ConcurrentHashMap 内部采用“锁分段”机制替代 Hashtable 的独占锁。进而提高性能。详解见https://blog.csdn.net/weixin_39407066/article/details/88697827此篇博客。
此包还提供了设计用于多线程Collection 实现:ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArrayList 和CopyOnWriteArraySet。当期望许多线程访问一个给定 collection 时,ConcurrentHashMap 通常优于同步的 HashMap,ConcurrentSkipListMap 通常优于同步的 TreeMap。当期望的读数和遍历远远大于列表的更新数时,CopyOnWriteArrayList 优于同步的 ArrayList。
ReadWriteLock 读写锁
ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持。写入锁是独占的。ReadWriteLock 读取操作通常不会改变共享资源,但执行写入操作时,必须独占方式来获取锁。 ReadWriteLock 能提供比独占锁更高的并发性。而对于只读的数据结构,其中包含的不变性可以完全不需要考虑加锁操作.写写和读写是互斥的, 读读不需要互斥。
线程池
详情见https://blog.csdn.net/weixin_39407066/article/details/88871585此篇文章。
Fork/Join 框架
从JDK1.7开始,Java提供Fork/Join框架用于并行执行任务,它的思想就是讲一个大任务分割成若干小任务,最终汇总每个小任务的结果得到这个大任务的结果。主要分两步:任务切分和结果合并。它的模型大致是这样的:线程池中的每个线程都有自己的工作队列(PS:这一点和ThreadPoolExecutor不同,ThreadPoolExecutor是所有线程公用一个工作队列,所有线程都从这个工作队列中取任务,所以当一个大任务分配成小任务由多个线程执行肯定比有一个线程执行效率高),当自己队列中的任务都完成以后,会从其它线程的工作队列中偷一个任务执行,这样可以充分利用资源,而窃取的时候为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。
Fork/Join 框架与线程池的区别
采用 “工作窃取”模式(work-stealing):当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,线程和队列一一对应,率先执行完毕的线程去其他线程的队列里窃取一个任务来执行。
相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上.在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行.这种方式减少了线程的等待时间,提高了性能。sync mode 默认为false, 当设置为true时,只有当任务未进行实际的递归分解时,它才执行异步任务,然后才实际查询asyncMode标志,在sync mode模式下,从未加入的分叉任务以LIFO(先进先出)顺序执行,而默认为false时是以LIFO(后进先出)顺序处理此类任务。什么时候使用哪种模式?每当选择ForkJoinPool线程池以利用其工作窃取属性而不是进行递归fork / join任务处理时,异步模式可能是更自然的选择,因为任务按提交顺序执行。有时会说异步事件驱动的框架(例如CompletableFuture)可以更效率。例如,在构造复杂的CompletableFuture回调链时,异步模式下的自定义ForkJoinPool执行程序的性能可能比默认执行程序稍好。
ForkJoinTask:ForkJoinTask代表运行在ForkJoinPool中的任务。
主要方法:
- fork() 在当前线程运行的线程池中安排一个异步执行。简单的理解就是再创建一个子任务。
- join() 当任务完成的时候返回计算结果。
- invoke() 开始执行任务,如果必要,等待计算完成。
子类:
- RecursiveAction 一个递归无结果的ForkJoinTask(没有返回值)
- RecursiveTask 一个递归有结果的ForkJoinTask(有返回值)
ForkJoinWorkerThread:ForkJoinWorkerThread代表ForkJoinPool线程池中的一个执行任务的线程.
WorkQueue是一个ForkJoinPool中的内部类,它是线程池中线程的工作队列的一个封装,支持任务窃取.
来源:CSDN
作者:weixin_39407066
链接:https://blog.csdn.net/weixin_39407066/article/details/104008826