1、ReentranLock的构造函数可以传入一个boolean参数,用来指定公平/非公平模式,默认是false非公平的。非公平的效率更高。
2、Lock的其他方法
tryLock() 尝试获得锁,返回true/false
tryLock(timeout,unit) 在给定的时间内尝试获得锁
isFair() 是否为公平锁
isLocked() 当前线程是否持有锁
lock.getHoldCount() 持有锁的数量,只能在当前调用线程内部使用,不能再其他线程中使用。
3、ReentrantReadWriteLock
读写锁,采用读写分离机制,高并发下读多写少时性能优于ReentrantLock
读读共享,写写互斥,读写互斥。
4、四种线程池
1、newCachedThreadPool具有缓存性质的线程池,
线程最大空闲时间60s,线程可重复利用(缓存特性),
没有最大线程数限制。任务耗时端,数量大。
2、newFixedThreadPool具有固定数量的线程池,
核心线程数等于最大线程数,
线程最大空闲时间为0,
3、newScheduledThreadPool 具有时间调度特性的线程池,必须初始化核心线程数,
底层使用DelayedWorkQueue实现延迟特性。
4、newSingleThreadExecutor 核心线程数与最大线程数均为1,用于不需要并发顺序执行。
四种线程池都是通过Executors类创建的,底层创建的都是ThreadPoolExecutor类,可以构建自己需要的线程类。
Future模式
JDK的Concurrent包提供了Future模式的实现,可以直接使用。
使用Future模式需要实现Callable接口,并使用FutureTask进行封装,使用线程池进行提交。
Master-Worker模式:
1、Master-Worker模式是一种将串行任务并行化的方案,被分解的子任务在系统中可以被并行处理,
同时,如果有需要,Master进程不需要等待所有的子任务都完成计算,就可以根据已有的部分结果集计算最终结果集。
2、客户端将所有任务提交给Master,Master分配Worker去并发处理任务,并将每一个任务的处理结果返回给Master,
所有的任务处理完毕后,由Master进行结果汇总再返回给Client。
高性能随机数
Random与ThreadLocalRandom在高并发场景下的性能差异和原理
1、Random存在性能缺陷,主要原因是要不断的计算新的种子,更新原种子,使用CAS方法。高并发的情况下会造成大量的线程
自旋,而只有一个线程会更新成功。
2、ThreadLocalRandom采用ThreadLocal的机制,每一个线程都是用自己的种子去进行计算下一个种子,规避CAS在并发下的问题。
高性能累加器LongAddr
1、AtomicLong存在性能瓶颈,由于使用CAS方法。高并发的情况下会造成大量的线程自旋,而只有一个线程会更新成功,
浪费CPU资源。
LongAdder的思想是将单一的原子变量拆分为多个变量,从而降低高并发下的资源争抢。
increment、longValue、LongAdder是LongAccumulator的特例,DoubleAdder是DoubleAccumulator的特例,
Accumulator的特点是可以设置初始值,自定义累加算法。
COWIterator的弱一致性
使用COW容器的iterator方法实际返回的是COWIterator实例,遍历的数据为快照数据,其他线程对于容器元素增加,
删除,修改不对快照产生影响。
对java.util.concurrent.CopyOnWriteArrayList、java.util.concurrent.CopyOnWriteArraySet均适用。
LockSupport
1、LockSupport的底层采用Unsafe类来实现,他是其他同步类的阻塞与唤醒的基础。
2、park与unpark需要成对使用,parkUntil与parkNanos可以单独使用。
3、先调用unpark再调用park会导致park失效。
4、线程中断interrupt会导致park失效并且不抛异常。
5、例如blocker可以对堆栈进行追踪,结合jstack进行使用。
AQS抽象同步队列
1、抽象队列同步器简称AQS,是同步器的基础组件,JUC种锁的底层实现均依赖于AQS,开发不需要使用。
2、采用FIFO的双向队列实现,队列元素为Node (静态内部类),Node内的thread变量用于存储进入队列的线程。
3、Node节点内部的Shared用来标记该线程是获取共享资源时被阻塞挂起后放入AQS队列的,
EXCLUSIVE用来标记线程是获取独占资源时被挂起后放入AQS队列的。
WaitStatus记录当前线程等待状态,可以为CANCELLED(线程被取消了)、SIGNAL(线程需要被唤醒)、
CONDITION(线程在条件队列里面等待)、PROPAGATE(释放共享资源时需要通知其他节点);prev记录当前节点的前驱节点,next记录当前节点的后继节点。
下面案例说明线程在条件队列里面等待
package com.mkevin.demo8;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 观察Condition的运行机制
*/
public class AQSDemo1 {
static ReentrantLock rl = new ReentrantLock();
static Condition condition = rl.newCondition();
public static void main(String[] args) throws InterruptedException {
new Runner().start();
new Runner().start();
new Runner().start();
}
static class Runner extends Thread{
@Override
public void run() {
try {
Thread.sleep(1000);
rl.lock();
System.out.println(Thread.currentThread().getName()+" before await..");
condition.await();
System.out.println(Thread.currentThread().getName()+" after await..");
rl.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
下面是执行的结果:
Thread-0 before await..
Thread-2 before await..
Thread-1 before await..
4、在AQS中维持了一个单一的状态信息state,可以通过getState、setState、compareAndSetState函数修改其值。对于ReentrantLock的实现来说,state可以用来表示
当前线程获取锁的可重入次数;对于读写锁ReentrantReadWriteLock来说,state的高16位表示可读状态,也就是获取该读锁的次数,低16位表示获取到写锁的线程的可重入次数;
对于semaphore来说,state用来表示当前可用信号的个数;
对于CountDownlatch来说,state用来表示计数器当前的值。
Phaser移相器
在JDK7中加入的并发工具类
1、通过phaser同步的线程被称为party。所有需要同步的party必须持有同一个phaser对象。
party需要向phaser注册,执行phaser.register()方法注册,该方法仅仅是增加phaser中的线程计数。
也可以通过构造器注册,new Phaser(3) 就会在创建phaser对象时注册3个party
2、unarrived party到达一个phaser (阶段)之前处于unarrived状态。
3、arrived到达时处于arrived状态,一个arrived的party也被称为arrival
4、deregister
一个线程可以在arrive某个phase后退出deregister,与参赛者中途退赛相同,可以使用
arriveAndDeregister()方法来实现。(到达并注销)
package com.mkevin.demo10;
import java.util.concurrent.Phaser;
import java.util.concurrent.ThreadLocalRandom;
/**
* Phaser的正常使用
*/
public class PhaserDemo1 {
public static void main(String[] args) throws InterruptedException {
//Phaser(5)代表注册的party数量, 不传入默认为0
Phaser phaser = new Phaser(5);
new Runner(phaser).start();
Thread.sleep(ThreadLocalRandom.current().nextInt(1000));
new Runner(phaser).start();
Thread.sleep(ThreadLocalRandom.current().nextInt(1000));
new Runner(phaser).start();
Thread.sleep(ThreadLocalRandom.current().nextInt(1000));
new Runner(phaser).start();
Thread.sleep(ThreadLocalRandom.current().nextInt(1000));
new Runner(phaser).start();
}
static class Runner extends Thread{
private Phaser phaser;
// 多个线程必须持有同一个phaser
public Runner(Phaser phaser){
this.phaser=phaser;
}
@Override
public void run() {
try {
System.out.println(this.getName()+" is ready1");
phaser.arriveAndAwaitAdvance();
System.out.println(this.getName()+" running...");
Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
System.out.println(this.getName()+" is ready2");
phaser.arriveAndAwaitAdvance();
System.out.println(this.getName()+" running...");
Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
System.out.println(this.getName()+" is ready3");
phaser.arriveAndAwaitAdvance();
Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
System.out.println(this.getName()+" over");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
下面是执行的结果:
"C:\Program Files\Java\jdk1.8.0_221\bin\java.exe" "-javaagent:D:\IntellJ\IntelliJ IDEA 2019.3.4\lib\idea_rt.jar=51731:D:\IntellJ\IntelliJ IDEA 2019.3.4\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_221\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\rt.jar;C:\Users\weizhen\Downloads\e2d60ba87a7a8405405d05627e31e279\JAVA并发编程核心技术精讲【346812】JOIN方法详细解析\mima-concurrent\target\classes" com.mkevin.demo10.PhaserDemo1
Thread-0 is ready1
Thread-1 is ready1
Thread-2 is ready1
Thread-3 is ready1
Thread-4 is ready1
Thread-3 running...
Thread-2 running...
Thread-1 running...
Thread-0 running...
Thread-4 running...
Thread-4 is ready2
Thread-0 is ready2
Thread-2 is ready2
Thread-1 is ready2
Thread-3 is ready2
Thread-1 running...
Thread-3 running...
Thread-4 running...
Thread-2 running...
Thread-0 running...
Thread-0 is ready3
Thread-4 is ready3
Thread-3 is ready3
Thread-1 is ready3
Thread-2 is ready3
Thread-4 over
Thread-0 over
Thread-2 over
Thread-3 over
Thread-1 over
Process finished with exit code 0
自定义phaser类继承phaser接口,实现onAdvance方法。
package com.mkevin.demo10;
import com.mkevin.entity.P;
import java.util.concurrent.Phaser;
import java.util.concurrent.ThreadLocalRandom;
public class PhaserDemo2 {
public static void main(String[] args) throws InterruptedException {
P.o("Phaser's onAdvance 使用: 到达每个阶段执行");
Phaser phaser = new MKevinPhaser(5, 3);
System.out.println("需要参与者数量:" + phaser.getRegisteredParties());
new Runner(phaser).start();
Thread.sleep(ThreadLocalRandom.current().nextInt(1000));
new Runner(phaser).start();
Thread.sleep(ThreadLocalRandom.current().nextInt(1000));
new Runner(phaser).start();
}
static class MKevinPhaser extends Phaser {
//总计阶段数量
private int totalPhaseNum = 3;
public MKevinPhaser(int totalPhaseNum, int parties) {
super(parties);
this.totalPhaseNum = totalPhaseNum;
}
@Override
protected boolean onAdvance(int phase, int registeredParties) {
System.out.println("phase " + phase + " is over, registeredParties is " + registeredParties);
//如果已经到达了最后一个阶段,或者参与者为0,则结束
return (totalPhaseNum - 1) == phase || registeredParties == 0;
}
}
static class Runner extends Thread {
private Phaser phaser;
public Runner(Phaser phaser) {
this.phaser = phaser;
}
@Override
public void run() {
try {
System.out.println(this.getName() + " is ready1");
phaser.arriveAndAwaitAdvance();
System.out.println(this.getName() + " running...");
Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
System.out.println(this.getName() + " is ready2");
phaser.arriveAndAwaitAdvance();
System.out.println(this.getName() + " running...");
Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
System.out.println(this.getName() + " is ready3");
phaser.arriveAndAwaitAdvance();
System.out.println(this.getName() + " running...");
Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
System.out.println(this.getName() + " is ready4");
phaser.arriveAndAwaitAdvance();
System.out.println(this.getName() + " running...");
Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
System.out.println(this.getName() + " is ready5");
phaser.arriveAndAwaitAdvance();
Thread.sleep(ThreadLocalRandom.current().nextInt(3000));
System.out.println(this.getName() + " over");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
下面是运行的结果:
---------------------------------------------
Phaser's onAdvance 使用: 到达每个阶段执行
---------------------------------------------
需要参与者数量:3
Thread-0 is ready1
Thread-1 is ready1
Thread-2 is ready1
phase 0 is over, registeredParties is 3
Thread-1 running...
Thread-0 running...
Thread-2 running...
Thread-0 is ready2
Thread-1 is ready2
Thread-2 is ready2
phase 1 is over, registeredParties is 3
Thread-1 running...
Thread-0 running...
Thread-2 running...
Thread-2 is ready3
Thread-1 is ready3
Thread-0 is ready3
phase 2 is over, registeredParties is 3
Thread-2 running...
Thread-0 running...
Thread-1 running...
Thread-0 is ready4
Thread-2 is ready4
Thread-1 is ready4
phase 3 is over, registeredParties is 3
Thread-1 running...
Thread-0 running...
Thread-2 running...
Thread-2 is ready5
Thread-0 is ready5
Thread-1 is ready5
phase 4 is over, registeredParties is 3
Thread-2 over
Thread-1 over
Thread-0 over
Process finished with exit code 0
Phaser到达某个阶段后,一部分Phaser中途退出,另一部分继续执行:
package com.mkevin.demo10;
import com.mkevin.entity.P;
import java.util.concurrent.Phaser;
public class PhaserDemo3 {
public static void main(String[] args) throws InterruptedException {
P.o("Phaser's register 使用: 到达每个阶段执行");
Phaser phaser = new Phaser();
for (int i = 0; i < 4; i++) {
phaser.register();
new Thread(new Runner(phaser, i)).start();
}
}
static class Runner implements Runnable {
private Phaser phaser;
private int no;
public Runner(Phaser phaser, int no) {
this.phaser = phaser;
this.no = no;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " start");
phaser.arriveAndAwaitAdvance();
System.out.println(Thread.currentThread().getName() + " running 1");
if (no == 1) {
// int a = 10 / 0;
phaser.arriveAndDeregister();
return;
}
System.out.println(Thread.currentThread().getName() + " running 2");
if (no == 3) {
phaser.arriveAndDeregister();
return;
}
phaser.arriveAndAwaitAdvance();
System.out.println(Thread.currentThread().getName() + " end");
} catch (Exception ex) {
ex.printStackTrace();
phaser.arriveAndDeregister();
}
}
}
}
下面是运行的结果:
---------------------------------------------
Phaser's register 使用: 到达每个阶段执行
---------------------------------------------
Thread-0 start
Thread-1 start
Thread-2 start
Thread-3 start
Thread-3 running 1
Thread-0 running 1
Thread-0 running 2
Thread-1 running 1
Thread-2 running 1
Thread-2 running 2
Thread-3 running 2
Thread-2 end
Thread-0 end
Process finished with exit code 0
StampedLock简介
StampedLock类,在JDK8中加入全路径为java.util.concurrent.locks.StampedLock。
功能与RRW (ReentrantReadWriteLock) 功能类似提供三种读写锁。
StampedLock中引入了一个stamp (邮戳)的概念。它代表线程获取到锁的版本,每一把锁都有一个唯一的stamp。
写锁writeLock,是排它锁、也叫独占锁,相同时间只能有一个线程获取锁,其他线程请求读锁和写锁都会被阻塞。
功能类似于ReetrantReadWriteLock.writeLock
区别是StampedLock的写锁是不可重入锁。当前没有线程持有读锁或写锁的时候才可以获得获取到该锁。
stampedLock获取锁:
P.o("StampedLock的WriteLock正常使用");
//创建StampedLock对象
StampedLock sl = new StampedLock();
//获取读锁,并且返回stamp
long stamp = sl.writeLock();
System.out.println("get write lock,stamp="+stamp);
//使用完毕,释放锁,但是要传入对应的stamp
sl.unlockWrite(stamp);
//再次获取写锁
stamp = sl.writeLock();
System.out.println("get write lock,stamp="+stamp);
//释放写锁
sl.unlockWrite(stamp);
下面是获取到锁的时间戳
---------------------------------------------
StampedLock的WriteLock正常使用
---------------------------------------------
get write lock,stamp=384
get write lock,stamp=640
Process finished with exit code 0
下面这段stampLock一个线程重新获得锁,不会成功。
writeLock与unlockWrite必须成对使用,解锁时必须需要传入相对应的stamp才可以释放锁。每次获得锁之后都会得到一个新的stamp值。
P.o("StampedLock的WriteLock非正常使用:非重入锁");
StampedLock sl = new StampedLock();
long stamp = sl.writeLock();
System.out.println("get write lock,stamp="+stamp);
stamp = sl.writeLock();
System.out.println("get write lock,stamp="+stamp);
sl.unlockWrite(stamp);
下面是执行结果:
---------------------------------------------
StampedLock的WriteLock非正常使用:非重入锁
---------------------------------------------
get write lock,stamp=384
ReentrantReadWriteLock的可重入锁
同一个线程获取锁后,再次尝试获取锁而无法获取,则证明其为非重入锁。
对于ReentrantLock,同一个线程获取锁后,再次尝试获取锁可以获取,则证明其为重入锁。
对于ReentrantReadWriteLock.WriteLock,同一个线程获取写锁后,再次尝试获取锁依然可获取锁,则证明其为重入锁。
P.o("ReentrantReadWriteLock:重入锁对比");
ReentrantReadWriteLock rrw = new ReentrantReadWriteLock();
ReentrantReadWriteLock.WriteLock writeLock = rrw.writeLock();
writeLock.lock();
System.out.println("get ReentrantReadWriteLock.WriteLock lock1");
writeLock.lock();
System.out.println("get ReentrantReadWriteLock.WriteLock lock2");
writeLock.unlock();
下面是可重入锁的执行结果,同一个线程是可以重复获得到锁的:
---------------------------------------------
ReentrantReadWriteLock:重入锁对比
---------------------------------------------
get ReentrantReadWriteLock.WriteLock lock1
get ReentrantReadWriteLock.WriteLock lock2
StampedLock获取不到锁就不阻塞
StampedLock的写锁是不可重入锁
P.o("StampedLock.tryWriteLock:尝试获取,如果能获取则获取锁,获取不到不阻塞");
StampedLock sl1 = new StampedLock();
long stamp1 = sl1.tryWriteLock();
System.out.println("get StampedLock.tryWriteLock lock1,stamp="+stamp1);
long stamp2 = sl1.tryWriteLock();
System.out.println("can not get StampedLock.tryWriteLock lock1,stamp="+stamp2);
long stamp3 = sl1.writeLock();
System.out.println("can not get StampedLock.writeLock lock2,stamp="+stamp3);
sl1.unlockWrite(stamp1);
下面是执行的结果,获取不到锁,这里就没有继续阻塞
---------------------------------------------
StampedLock.tryWriteLock:尝试获取,如果能获取则获取锁,获取不到不阻塞
---------------------------------------------
get StampedLock.tryWriteLock lock1,stamp=384
can not get StampedLock.tryWriteLock lock1,stamp=0
StampedLock的读锁
悲观读锁是一个共享锁,没有线程占用写锁的情况下,多个线程可以同时获取读锁。如果其他线程已经获得了写锁,则阻塞当前线程。
P.o("StampedLock.readLock正常使用:注意每次获得的写锁stamp不同,必须所有的读锁都释放才可以获得写锁");
StampedLock sl = new StampedLock();
long stamp1 = sl.readLock();
System.out.println("get read lock1,stamp="+stamp1);
long stamp2 = sl.readLock();
System.out.println("get read lock2,stamp="+stamp2);
sl.unlockRead(stamp1);
sl.unlockRead(stamp2);
long stamp3 = sl.writeLock();
System.out.println("get write lock,stamp="+stamp3);
下面是执行的结果:
StampedLock.readLock正常使用:注意每次获得的写锁stamp不同,必须所有的读锁都释放才可以获得写锁
---------------------------------------------
get read lock1,stamp=257
get read lock2,stamp=258
get write lock,stamp=384
--------------------------------
StampedLock存在读锁没有释放,所以不能获取到写锁,就是会一直阻塞住。
P.o("StampedLock.readLock非正常使用:存在读锁未释放,无法获得写锁");
StampedLock sl1 = new StampedLock();
long stamp11 = sl1.readLock();
System.out.println("get read lock1,stamp="+stamp11);
long stamp21 = sl1.readLock();
System.out.println("get read lock2,stamp="+stamp21);
sl1.unlockRead(stamp11);
long stamp31 = sl1.writeLock();
System.out.println("get write lock,stamp="+stamp31);
下面是运行的结果,就是会一直阻塞住:
---------------------------------------------
StampedLock.readLock非正常使用:存在读锁未释放,无法获得写锁
---------------------------------------------
get read lock1,stamp=257
get read lock2,stamp=258
下面是StampedLock的正常使用,获取读锁,释放,获取写锁
P.o("StampedLock.readLock正常使用:获取读锁->释放->获取写锁");
StampedLock sl2 = new StampedLock();
long stamp12 = sl2.readLock();
System.out.println(Thread.currentThread().getName()+" get read lock1,stamp="+stamp12);
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" run");
long stamp121 = sl2.writeLock();
System.out.println(Thread.currentThread().getName()+" get write lock1,stamp="+stamp121);
}
}).start();
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName()+" get unlock read lock1,stamp="+stamp12);
sl2.unlockRead(stamp12);
下面是运行的结果:
---------------------------------------------
StampedLock.readLock正常使用:获取读锁->释放->获取写锁
---------------------------------------------
main get read lock1,stamp=257
Thread-0 run
main get unlock read lock1,stamp=257
Thread-0 get write lock1,stamp=384
StampedLock获取写锁,释放,获取读锁
P.o("StampedLock.readLock正常使用:获取写锁->释放->获取读锁");
StampedLock sl3 = new StampedLock();
long stamp13 = sl3.writeLock();
System.out.println(Thread.currentThread().getName()+" get write lock1,stamp="+stamp13);
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" run");
long stamp131 = sl3.readLock();
System.out.println(Thread.currentThread().getName()+" get read lock1,stamp="+stamp131);
}
}).start();
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName()+" get unlock write lock1,stamp="+stamp13);
sl3.unlockWrite(stamp13);
下面是运行的结果:
---------------------------------------------
StampedLock.readLock正常使用:获取写锁->释放->获取读锁
---------------------------------------------
main get write lock1,stamp=384
Thread-0 run
main get unlock write lock1,stamp=384
Thread-0 get read lock1,stamp=513
Process finished with exit code 0
StampLock使用快照的方式,需要复制一份要操作的变量到方法栈,
操作的数据只是一个快照,从而保证了数据的最终一致性。
但是读线程可能使用的数据不是最新的。特别适用于读多写少的高并发场景
乐观读锁:
原理很简单,就是我先尝试获取,这时候没有写锁我就拿到了一个锁,在我真正要使用的时候我再验证一下是否发生了改变,如果没有发生改变就可以安心使用。
如果某个线程已经获取了写锁,这时候再尝试获取乐观锁也是可以获取的,只是得到的stamp为0,无法通过validate验证。
乐观读锁本质上并未加锁,而是提供了获取和检测的方法,由程序人员来控制该做些什么。
虽然性能大大提升,但是却增加了开发人员的复杂度,如果不是特别高的并发场景,对性能不要求极致,可以不考虑使用。
tryOptimisticRead与validate一定要紧紧挨着使用,否则在获取和验证之间很可能数据被修改。
如果这期间锁发生变化则validate返回false,否则返回true。
StampedLock sl = new StampedLock();
//乐观读
long stamp = sl.tryOptimisticRead();
System.out.println(Thread.currentThread().getName()+" tryOptimisticRead stamp="+stamp);
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" run");
long stamp131 = sl.writeLock();
System.out.println(Thread.currentThread().getName()+" get write lock1,stamp="+stamp131);
sl.unlockWrite(stamp131);
System.out.println(Thread.currentThread().getName()+" unlock write lock1,stamp="+stamp131);
}
}).start();
Thread.sleep(3000);
//验证是否发生过变化
boolean validate = sl.validate(stamp);
System.out.println(Thread.currentThread().getName()+" tryOptimisticRead validate="+validate);
stamp = sl.tryOptimisticRead();
System.out.println(Thread.currentThread().getName()+" tryOptimisticRead stamp="+stamp);
validate = sl.validate(stamp);
下面是执行的结果(由执行结果可以看出乐观读锁,不是真正的锁):
main tryOptimisticRead stamp=256
Thread-0 run
Thread-0 get write lock1,stamp=384
Thread-0 unlock write lock1,stamp=384
main tryOptimisticRead validate=false
main tryOptimisticRead stamp=512
Process finished with exit code 0
锁的分类:
1、乐观锁和悲观锁的概念来自于数据库
2.悲观锁对数据被修改持悲观态度,认为数据很容易就会被其他线程修改,所以在处理数据之前先加锁,处理完毕释放锁。
3.乐观锁对数据被修改持乐观态度,认为数据一般情况下不会被其他线程修改,所以在处理数据之前不会加锁,而是在数据进行更新时进行冲突检测。
4.对于数据库的悲观锁就是排它锁,在处理数据之前,先尝试给记录加排它锁,如果成功则继续处理,如果失败则挂起或抛出异常,直到数据处理完毕释放锁。
5.对于数据库的乐观锁所典型的就是CAS方式更新, 例如:update name=‘kevin’ where id=1 and name=‘kevin0’,在更新数据的时候校验这个值是否发生了变化,类似于CAS的操作。
公平锁与非公平锁:
1.据线程获取锁的抢占机制,锁可以分为公平锁和非公平锁,最早请求锁的线程将最早获取到锁。而非公平锁则先请求不一定先得。JUC中的ReentrantLock提供了公平和非公平锁特性。·
2.公平锁:ReentrantLockpairLock= new ReentrantLock(true)
3.非公平锁:ReentrantLockpairLock= new ReentrantLock(false),如果构造函数不传递参数,则默认是非公平锁。
4.非必要情况下使用非公平锁,公平锁存在性能开销
独占锁与共享锁
1.只能被单个线程所持有的锁是独占锁,可以被多个线程持有的锁是共享锁。
2.ReentrantLock就是以独占方式实现的,属于悲观锁
3.ReadWriteLock读写锁是以共享锁方式实现的,属于乐观锁
4.StampedLock的写锁,属于悲观锁。
5.StampedLock的乐观读锁,悲观读锁、属于乐观锁。
可重入锁
1.当一个线程想要获取本线程已经持有的锁时,不会被阻塞,而是能够再次获得这个锁,这就是重入锁。
2.Synchornized是一种可重入锁,内部维护一个线程标志(谁持有锁),以及一个计数器。
3.ReentrantLock也是一种可重入锁
4.ReadWriteLock、StampedLock的读锁也是可重入锁
自旋锁
1.当获取锁的时候如果发现锁已经被其他线程占有,则不阻塞自己,也不释放CPU使用权,而是尝试多次获取,如果尝试了指定次数之后仍然没有获得锁,再阻塞线程。
2.自旋锁认为锁不会被长时间持有,使用CPU时间来换取线程上下文切换的开销,从而提高性能。但是可能会浪费CPU资源。
3.-XX:PreBlockSpin=n可以设置自旋次数(已经成为了历史),在Jdk7u40时被删除了,其实在jkd6的时候就已经无效了,现在HotSpotVM采用的是adaptive spinning(自适应自旋),虚拟机会根据情况来对每个线程使用不同的自旋次数。
通常使用线程池的submit方法将任务提交到线程池内执行。
如果此时线程池内有空闲的线程,则会立即执行该任务,如果没有则需要根据线程池的类型选择等待,或者新建线程。
线程池内的线程并不是线程池对象初始化的时候就创建好的,而是当有任务被提交进来之后才创建的,
创建线程的过程是无法干预的。
使用ThreadFactory
第一步:编写ThreadFactory接口的实现类
第二步:创建线程池时传入ThreadFactory对象
下面代码讲的是ThreadFactory初始化的时候动态输出日志的程序
package com.mkevin.demo13;
import com.mkevin.entity.P;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadLocalRandom;
/**
* 线程工场
*/
class DemoThreadFactory implements ThreadFactory {
// 是否记录日志
private boolean saveLog;
// 工场名称
private String factoryName;
public DemoThreadFactory(String factoryName, boolean saveLog) {
this.factoryName = factoryName;
this.saveLog = saveLog;
}
@Override
public Thread newThread(Runnable r) {
if (saveLog) {
//动态输出日志
P.l(System.currentTimeMillis() + this.factoryName + " create start");
}
Thread thread = new Thread(r);
//自定义线程名字
thread.setName("Kevin-Thread-" + thread.getName() + ":" + thread.getId());
try {
//模拟初始化时间
Thread.sleep(ThreadLocalRandom.current().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
if (saveLog) {
P.l(System.currentTimeMillis() + this.factoryName + " " + thread.getName() + " create end");
}
return thread;
}
}
下面是自定义线程工厂的使用:
package com.mkevin.demo13;
import com.mkevin.entity.P;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 使用线程工场创建线程
*/
public class ThreadFactoryDemo0 {
public static void main(String[] args) {
//创建任务对象
Runnable runnable = new Runnable() {
@Override
public void run() {
P.l(Thread.currentThread().getName() + " run");
}
};
P.o("newCachedThreadPool使用ThreadFactory");
//创建自定义线程工场
DemoThreadFactory threadFactory = new DemoThreadFactory("newCachedThreadPoolFactory",true);
//使用线程工场初始化线程池
ExecutorService executorService = Executors.newCachedThreadPool(threadFactory);
for (int i = 0; i < 3; i++) {
executorService.execute(runnable);
}
executorService.shutdown();
P.o("newFixedThreadPool使用ThreadFactory");
//创建自定义线程工场
threadFactory = new DemoThreadFactory("newFixedThreadPoolFactory",true);
//使用线程工场初始化线程池
executorService = Executors.newFixedThreadPool(2, threadFactory);
for(int i=0;i<3;i++){
executorService.execute(runnable);
}
executorService.shutdown();
//创建其他线程池也可以使用ThreadFactory
//Executors.newScheduledThreadPool(2,threadFactory);
//Executors.newSingleThreadExecutor(threadFactory);
//Executors.newSingleThreadScheduledExecutor(threadFactory);
}
}
下面是线程工厂的运行结果:
---------------------------------------------
newCachedThreadPool使用ThreadFactory
---------------------------------------------
1608532534788newCachedThreadPoolFactory create start
1608532534951newCachedThreadPoolFactory Kevin-Thread-Thread-0:11 create end
1608532534951newCachedThreadPoolFactory create start
Kevin-Thread-Thread-0:11 run
1608532535264newCachedThreadPoolFactory Kevin-Thread-Thread-1:12 create end
Kevin-Thread-Thread-0:11 run
---------------------------------------------
newFixedThreadPool使用ThreadFactory
---------------------------------------------
Kevin-Thread-Thread-1:12 run
1608532535264newFixedThreadPoolFactory create start
1608532535846newFixedThreadPoolFactory Kevin-Thread-Thread-2:13 create end
1608532535846newFixedThreadPoolFactory create start
Kevin-Thread-Thread-2:13 run
1608532536590newFixedThreadPoolFactory Kevin-Thread-Thread-3:14 create end
Kevin-Thread-Thread-2:13 run
Kevin-Thread-Thread-3:14 run
Process finished with exit code 0
线程池发生异常的状态捕获
1、线程池内运行的线程如果发生异常,一定要捕获
2、可以采用更优雅的方式处理所有线程的异常,例如记录日志,发送预警消息等。
3、结合ThreadFactory以及线程的setUncaughtExceptionHandler方法来处理最好
4、对execute提交的任务有效,对submit提交的任务无效!
// 创建线程池,并向线程池提交任务,则创建线程,任务执行完毕而线程不销毁,JVM继续运行
// 等待60秒后,自动销毁空闲线程,JVM退出
Runner runner = new Runner();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.submit(runner);
P.l("main is over");
下面显示60秒后线程自动退出
main is over
pool-1-thread-1 begin
pool-1-thread-1 end
创建线程池,向线程池提交任务,任务执行完毕而线程不销毁,JVM继续运行
始终保持1个线程存活,所以JVM不会退出。
// 创建线程池,并向线程池提交任务,则创建线程,任务执行完毕而线程不销毁,JVM继续运行
// 始终保持有1个线程存活,所以JVM不会退出
Runner runner = new Runner();
ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.submit(runner);
P.l("main is over");
下面的执行结果显示那个线程一直没有退出
main is over
pool-1-thread-1 begin
pool-1-thread-1 end
关闭线程池
1、shutdown让线程池内的任务继续执行完毕,但是不允许新的任务提交。
2、shutdown方法不阻塞, 等所有线程执行完毕后,销毁线程。
3、shutdown之后提交的任务会抛出RejectedExecutionException异常,代表拒绝接收。
4、shutdownNow之后提交的任务会抛出RejectedExecutionException异常,代表拒绝接收。
5、shutdownNow之后会引发sleep、join、wait方法的InterruptedException异常。
6、如果任务中没有触发InterruptedException的条件,则任务会继续运行直到结束。
线程池的各种状态
7、isShutdown用来判断线程池是否已经关闭。
8、isTerminated任务全部执行完毕,并且线程池已经关闭,才会返回true。
9、awaitTermination 阻塞,直到所有任务在关闭请求后完成执行,或发生超时,或当前线程中断。
shutdown让线程池内的任务继续执行完毕,但是不允许新的任务提交,shutdow方法不阻塞,
在fixThreadPool执行完任务后,停掉线程,释放资源等所有线程执行完毕后, 销毁线程,JVM退出。
//shutdown让线程池内的任务继续执行完毕,但是不允许新的任务提交,shutdow方法不阻塞
//等所有线程执行完毕后, 销毁线程,JVM退出
Runner runner = new Runner();
ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.submit(runner);
executorService.shutdown();
P.l("main is over");
下面是执行的结果:
pool-1-thread-1 begin
main is over
pool-1-thread-1 end
Process finished with exit code 0
shutdown之后提交的任务会报RejectedExecutionException异常,代表拒绝接收
Runner runner = new Runner();
ExecutorService executorService = Executors.newFixedThreadPool(3);
executorService.submit(runner);
executorService.shutdown();
P.l("main is over");
executorService.submit(runner);
下面是执行的结果:
main is over
pool-1-thread-1 begin
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@677327b6 rejected from java.util.concurrent.ThreadPoolExecutor@14ae5a5[Shutting down, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
at com.mkevin.demo14.ThreadPoolDemo1.main(ThreadPoolDemo1.java:44)
pool-1-thread-1 end
Process finished with exit code 1
如果任务中没有触发InterruptedException的条件,则会继续执行直到停止。
ExecutorService executorService = Executors.newFixedThreadPool(5);
executorService.submit(new Runner1());
executorService.shutdownNow();
P.l("main is over");
下面是一直在运行
535061931
pool-1-thread-1 1608535061931
pool-1-thread-1 1608535061931
pool-1-thread-1 1608535061931
pool-1-thread-1 1608535061931
pool-1-thread-1 1608535061931
pool-1-thread-1 1608535061931
pool-1-thread-1 1608535061931
pool-1-thread-1 1608535061931
Process finished with exit code -1
可以在任务中判断Thread.currentThread().isInterrupted()来规避shutdownNow的问题
ExecutorService executorService = Executors.newFixedThreadPool(5);
executorService.submit(new Runner2());
executorService.shutdownNow();
P.l("main is over");
下面是运行的结果
pool-1-thread-1 1608535331388
main is over
pool-1-thread-1 interrupted
Process finished with exit code 0
核心线程预启动策略
1、默认情况下,核心线程只有在任务提交的时候才会创建。
2、而预启动策略,可以让核心线程提前启动,从而增强最初提交的线程运行性能。
3、prestartCoreThread启动1个核心线程,覆盖仅在执行新任务时启动核心线程的默认策略。
如果所有核心线程都已启动,则此方法将返回false。
4、prestartAllCoreThreads启动所有核心线程。覆盖仅在执行新任务时启动核心线程的默认策略。
如果核心线程全部启动后再次调用,则会返回0。
核心线程预启动策略
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 5,
TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
P.l("线程池中的线程数:" + executor.getPoolSize());
P.l(executor.prestartCoreThread());
P.l("线程池中的线程数:" + executor.getPoolSize());
P.l(executor.prestartCoreThread());
P.l("线程池中的线程数:" + executor.getPoolSize());
P.l(executor.prestartCoreThread());
P.l(executor.prestartCoreThread());
P.l("线程池中的线程数:" + executor.getPoolSize());
下面是执行的结果:
线程池中的线程数:0
true
线程池中的线程数:1
true
线程池中的线程数:2
false
false
线程池中的线程数:2
预启动所有核心线程
Runner runner = new Runner();
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 5,
TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
P.l("线程池中的线程数:" + executor.getPoolSize());
P.l("启动核心线程数量为:" +executor.prestartAllCoreThreads());
P.l("线程池中的线程数:" + executor.getPoolSize());
P.l("启动核心线程数量为:" +executor.prestartAllCoreThreads());
下面是执行的结果
线程池中的线程数:0
启动核心线程数量为:2
线程池中的线程数:2
启动核心线程数量为:0
Java ThreadPool线程例子:
package com.mkevin.demo18;
import com.mkevin.entity.P;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolGetDemo {
public static void main(String[] args) throws InterruptedException {
Runner runner = new Runner();
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 5,
TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
executor.execute(runner);
executor.execute(runner);
executor.execute(runner);
executor.execute(runner);
Thread.sleep(1000);
P.l("返回核心线程数getCorePoolSize:"+executor.getCorePoolSize());
P.l("返回当前线程池中的线程数getPoolSize:"+executor.getPoolSize());
P.l("返回最大允许的线程数getMaximumPoolSize:"+executor.getMaximumPoolSize());
P.l("返回池中同时存在的最大线程数getLargestPoolSize:"+executor.getLargestPoolSize());
P.l("预定执行的任务总和getTaskCount:"+executor.getTaskCount());
P.l("当前线程池已经完成的任务数getCompletedTaskCount:"+executor.getCompletedTaskCount());
P.l("正在执行任务的线程的大致数目getActiveCount:"+executor.getActiveCount());
P.l("空闲时间getKeepAliveTime:"+executor.getKeepAliveTime(TimeUnit.SECONDS));
P.l("------------------------------------");
Thread.sleep(1100);
P.l("当前线程池已经完成的任务数getCompletedTaskCount:"+executor.getCompletedTaskCount());
Thread.sleep(1100);
P.l("当前线程池已经完成的任务数getCompletedTaskCount:"+executor.getCompletedTaskCount());
executor.shutdown();
P.l("当前线程池已经完成的任务数getCompletedTaskCount:"+executor.getCompletedTaskCount());
}
static class Runner implements Runnable {
@Override
public void run() {
P.l(Thread.currentThread().getName() + " run " + System.currentTimeMillis()+ "start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
P.l(Thread.currentThread().getName() + " run " + System.currentTimeMillis()+ "end");
}
}
}
下面是运行的结果:
pool-1-thread-2 run 1608605075322start
pool-1-thread-1 run 1608605075322start
返回核心线程数getCorePoolSize:2
返回当前线程池中的线程数getPoolSize:2
返回最大允许的线程数getMaximumPoolSize:2
返回池中同时存在的最大线程数getLargestPoolSize:2
预定执行的任务总和getTaskCount:4
当前线程池已经完成的任务数getCompletedTaskCount:0
正在执行任务的线程的大致数目getActiveCount:2
空闲时间getKeepAliveTime:5
------------------------------------
pool-1-thread-2 run 1608605076338end
pool-1-thread-1 run 1608605076338end
pool-1-thread-1 run 1608605076338start
pool-1-thread-2 run 1608605076338start
pool-1-thread-2 run 1608605077338end
pool-1-thread-1 run 1608605077338end
当前线程池已经完成的任务数getCompletedTaskCount:4
当前线程池已经完成的任务数getCompletedTaskCount:4
当前线程池已经完成的任务数getCompletedTaskCount:4
Process finished with exit code 0
CompletionService
CompletionService的使用模式,不断地提交任务给executor处理。
处理后的结果都会自动放入BlockedQueue。
另外一个线程不断地从队列里面取得处理的结果。
哪个任务先处理完,就能先得到哪个结果。
最后做汇总处理。
线程池Executor加上阻塞队列BlockedQueue
线程池Executor用来处理任务。
BlockedQueue用来获取每个线程的运行结果。
CompletionService 用来获取线程执行的结果
1、阻塞take 获取已经处理结束的结果,最先处理结束的,最先得到
Future f = cs.take();
2、非阻塞-poll,获取不到返回null
Future f = cs.poll();
3、阻塞固定时间-到时间获取不到返回null
Future f = cs.poll(2,TimeUnit.SECONDS);
package com.mkevin.demo11;
import java.util.concurrent.*;
public class CompletionServiceDemo0 {
public static void main(String[] args) {
try {
//创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 10, 5, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
//创建CompletionService, 需要借助Executor来实现
CompletionService cs = new ExecutorCompletionService(executor);
//创建回调线程
SalaryCallable0 sc1 = new SalaryCallable0(300, 3000);
SalaryCallable0 sc2 = new SalaryCallable0(200, 2000);
SalaryCallable0 sc3 = new SalaryCallable0(100, 1000);
//提交到线程池执行
cs.submit(sc1);
cs.submit(sc2);
cs.submit(sc3);
for (int i = 0; i < 3; i++) {
//阻塞-take获取已经处理结束的结果, 最先处理结束的,最先得到
Future f = cs.take();
//非阻塞-poll, 获取不到则返回null
// Future f = cs.poll();
//阻塞固定时间-到时间获取不到则返回null
// Future f = cs.poll(2,TimeUnit.SECONDS);
System.out.println("result(" + i + ")=" + f.get());
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
使用CompletionService提交任务,让Runnable也具有获得结果的特性
package com.mkevin.demo11;
import java.util.concurrent.*;
/**
* 让Runnable也具有获得结果的特性
*/
public class CompletionServiceDemo2 {
public static void main(String[] args) {
try {
//创建结果对象,用于获取结果
Result result = new Result();
ExecutorService executorService = Executors.newCachedThreadPool();
CompletionService cs = new ExecutorCompletionService(executorService);
//创建Runnable对象
SalaryRunner2 runner1 = new SalaryRunner2(result, 10, 1000);
SalaryRunner2 runner2 = new SalaryRunner2(result, 20, 2000);
//提交对象, 并使用result对象接收结果
Future<Result> f1 = cs.submit(runner1,result);
Future<Result> f2 = cs.submit(runner2,result);
//获取结果
System.out.println("f1:" + f1.get().getValue());
System.out.println("f2:" + f2.get().getValue());
} catch (Exception e) {
e.printStackTrace();
}
}
}
下面是运行的结果:
f1:10000
f2:20000
Process finished with exit code -1
下面展示一个线程不断产生任务,另外一个线程不断消费任务
package com.mkevin.demo11;
import java.util.concurrent.*;
public class CompletionServiceDemo3 {
public static void main(String[] args) throws InterruptedException {
//创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 10, 5, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
//创建CompletionService, 需要借助Executor来实现
CompletionService cs = new ExecutorCompletionService(executor);
//一个线程不断产生任务
Thread t0 = new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<100;i++){
SalaryCallable0 sc1 = new SalaryCallable0(300, ThreadLocalRandom.current().nextInt(2000));
cs.submit(sc1);
}
}
});
t0.start();
//一个线程不断处理结果
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//阻塞-take获取已经处理结束的结果, 最先处理结束的,最先得到
Future f = null;
try {
while (true) {
f = cs.take();
System.out.println(Thread.currentThread().getName() + " getResult = " + f.get());
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
});
t1.start();
t0.join();
t1.join();
executor.shutdown();
}
}
下面是运行的结果:
pool-1-thread-3 sleep=1734 salary*costTime=520200.0
pool-1-thread-1 sleep=683 salary*costTime=204900.0
pool-1-thread-2 sleep=158 salary*costTime=47400.0
pool-1-thread-2 sleep=977 salary*costTime=293100.0
Thread-1 getResult = 47400.0
Thread-1 getResult = 204900.0
pool-1-thread-1 sleep=1996 salary*costTime=598800.0
pool-1-thread-2 sleep=520 salary*costTime=156000.0
Thread-1 getResult = 293100.0
pool-1-thread-2 sleep=1204 salary*costTime=361200.0
Thread-1 getResult = 156000.0
pool-1-thread-3 sleep=450 salary*costTime=135000.0
Thread-1 getResult = 520200.0
Thread-1 getResult = 135000.0
pool-1-thread-3 sleep=990 salary*costTime=297000.0
pool-1-thread-1 sleep=1085 salary*costTime=325500.0
Thread-1 getResult = 598800.0
pool-1-thread-2 sleep=669 salary*costTime=200700.0
Thread-1 getResult = 361200.0
pool-1-thread-3 sleep=112 salary*costTime=33600.0
Thread-1 getResult = 297000.0
Thread-1 getResult = 33600.0
pool-1-thread-3 sleep=1365 salary*costTime=409500.0
pool-1-thread-2 sleep=1221 salary*costTime=366300.0
Thread-1 getResult = 200700.0
pool-1-thread-1 sleep=51 salary*costTime=15300.0
Thread-1 getResult = 325500.0
Thread-1 getResult = 15300.0
ForkJoin使用
java.util.concurrent.RecursiveTask 递归任务
java.util.concurrent.RecursiveAction 递归活动
ForkJoin递归地去执行任务
package com.mkevin.demo12;
import java.util.concurrent.ForkJoinPool;
public class ForkJoinDemo0 {
public static void main(String[] args) throws InterruptedException {
//创建ForkJoinPool
ForkJoinPool pool = new ForkJoinPool();
//提交任务
pool.submit(new RecursiveAction0(0, 9));
Thread.sleep(Integer.MAX_VALUE);
}
}
RecursiveAction0这个类
package com.mkevin.demo12;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.ThreadLocalRandom;
public class RecursiveAction0 extends RecursiveAction {
private int beginValue;
private int endValue;
public RecursiveAction0(int beginValue, int endValue) {
super();
this.beginValue = beginValue;
this.endValue = endValue;
}
@Override
protected void compute() {
System.out.println(Thread.currentThread().getName() + " start compute");
try {
Thread.sleep(ThreadLocalRandom.current().nextInt(5000));
} catch (InterruptedException e) {
e.printStackTrace();
}
if (endValue - beginValue > 2) {
//计算中间值
int middelNum = (beginValue + endValue) / 2;
//左计算
RecursiveAction0 leftAction = new RecursiveAction0(beginValue, middelNum);
//右计算
RecursiveAction0 rightAction = new RecursiveAction0(middelNum + 1, endValue);
//拆解
this.invokeAll(leftAction, rightAction);
} else {
System.out.println(Thread.currentThread().getName() + " : " + beginValue + "-" + endValue);
}
}
}
下面是执行的结果:
ForkJoinPool-1-worker-1 start compute
ForkJoinPool-1-worker-1 start compute
ForkJoinPool-1-worker-2 start compute
ForkJoinPool-1-worker-2 start compute
ForkJoinPool-1-worker-3 start compute
ForkJoinPool-1-worker-1 start compute
ForkJoinPool-1-worker-0 start compute
ForkJoinPool-1-worker-2 : 5-7
ForkJoinPool-1-worker-1 : 0-2
ForkJoinPool-1-worker-3 : 8-9
ForkJoinPool-1-worker-0 : 3-4
Process finished with exit code -1