多线程与并发
线程与进程
- 线程的状态
- NEW (新生)
- RUNNABLE (运行)
- BLOCKED (阻塞)
- WAITING (等待)
- TIMED_WAITING (超时等待)
- TERMINATED (终止)
- 区别:进程是资源分配的最小单位,线程是程序执行的最小单位。线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式
线程的启动方式
//启动线程的5种方式
public static void main(String[] args) {
new MyThread().start();
new Thread(new MyRun()).start();
new Thread(()->{
System.out.println("Hello Lambda!");
}).start();
Thread t = new Thread(new FutureTask
t.start();
ExecutorService service = Executors.newCachedThreadPool();
service.execute(()->{
System.out.println("Hello ThreadPool");
});
service.shutdown();
}
优雅的启动线程
单独的资源类,线程操作资源类,耦合度降低
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
data.increment();
},"A").start();
}
}
// 资源类
class Data{
private int number = 0;
public synchronized void increment() {
doSomething……
}
}
线程同步与互斥
CAS、Synchronized、Lock
CAS
Compare and Swap,比较并交换。CAS由3个操作数:内存值、预期值、要修改的新值。当且仅当预期值和内存值相同时,将内存值修改为新值,否则什么都不做。该操作是一个原子操作,被广泛的应用于Java的底层实现中。
public final boolean compareAndSet(int expect, int update)
缺点:
- 循环会耗时
- 一次性只能保证一个共享变量的原子性
- 容易出现ABA问题,如下线程T2写回B后由T3写回A,线程T1认为值没有发生变化

原子引用,解决ABA问题
- AtomicReference 原子性对象读、写
- AtomicStampedReference基于时间戳解决ABA问题
- AtomicMarkableReference基于标记位解决ABA问题
java.util.concurrent.atomic包下提供了一些原子操作的常用类:
- AtomicBoolean
- AtomicInteger
- AtomicLong
- AtomicReference
- AtomicIntegerArray
- AtomicLongArray
- AtomicMarkableReference
- AtomicReferenceArray
- AtomicStampedReference
java8对JUC的增强:用LongAdder替代AtomicLong,更适合并发度比较高的场景
Atomic虽然好用,不过这些的操作在并发量很大的情况下,性能问题也会被相应的放大。其中getAndIncrement的实现代码如下
public final long getAndIncrement() {
return unsafe.getAndAddLong(this, valueOffset, 1L);
}
// unsafe类中的实现
public final long getAndAddLong(Object var1, long var2, long var4) {
long var6;
do {
var6 = this.getLongVolatile(var1, var2);
} while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
return var6;
}
在getAndAddLong实现中,为了实现正确的累加操作,如果并发量很大的话,cpu会花费大量的时间在试错上面,相当于一个spin的操作。如果并发量小的情况,这些消耗可以忽略不计。
LongAdder可以解决AtomicXX这样的业务缺陷,内部的实现有点类似ConcurrentHashMap的分段锁。最好的情况下,每个线程都有独立的计数器,这样可以大量减少并发操作。
Synchronized
三种应用方式
- 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
- 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
- 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码前要获得给定对象的锁
底层原理
对象的对象头中存在一个Monitor对象,Monitor对象由_EntryList、Owner、_WaitSet等组成。当Monitor被某个对象占用后,会处于锁定状态,Owner会指向持有Monitor对象的线程。_EntryList、_WaitSet用来存放进入或者等待获取锁的线程。
代码块同步
使用monitorenter和monitorexit指令,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置,当执行monitorenter指令时,当前线程将试图获取 objectref(即对象锁) 所对应的 monitor 的持有权,当 objectref 的 monitor 的进入计数器为 0,那线程可以成功取得 monitor,并将计数器值设置为 1,取锁成功。如果当前线程已经拥有 objectref 的 monitor 的持有权,那它可以重入这个 monitor ,重入时计数器的值也会加 1。倘若其他线程已经拥有 objectref 的 monitor 的所有权,那当前线程将被阻塞,直到正在执行线程执行完毕,即monitorexit指令被执行,执行线程将释放 monitor(锁)并设置计数器值为0 ,其他线程将有机会持有 monitor 。值得注意的是编译器将会确保无论方法通过何种方式完成,方法中调用过的每条 monitorenter 指令都有执行其对应 monitorexit 指令,而无论这个方法是正常结束还是异常结束。为了保证在方法异常完成时 monitorenter 和 monitorexit 指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有的异常,它的目的就是用来执行 monitorexit 指令。从字节码中也可以看出多了一个monitorexit指令,它就是异常结束时被执行的释放 monitor 的指令。
方法同步
当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有monitor(虚拟机规范中用的是管程一词), 然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放monitor。在方法执行期间,执行线程持有了monitor,其他任何线程都无法再获得同一个monitor。如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的monitor将在异常抛到同步方法之外时自动释放。
JVM对Synchronized的优化
- 先使用偏向锁,优先同一线程再次获取锁
- 如果失败就升级成CAS轻量级锁
- 如果再失败会进行短暂的自旋,防止线程被系统挂起
- 最后如果都失败,就升级成重量级锁
Lock
AQS(AbstractQueuedSynchronizer)
- 由一个state标志位、一个同步队列和多个等待队列组成
- state值为1时表示占用,其他线程需要进入同步队列等待
- 同步队列是一个双向链表,当获得锁的线程需要等待某个条件时,会进入Condition等待队列
- 当Condition条件满足时,线程会从等待队列从新进入同步队列进行获取锁的竞争

ReentrantLock
先通过CAS尝试获取锁。如果此时已经有线程占据了锁,那就加入AQS队列并且被挂起。当锁被释放之后,排在同步队列队首的线程会被唤醒,然后通过CAS再次尝试获取锁。这时,如果
- 非公平锁:如果同时还有另一个线程进来尝试获取,那么有可能会让这个线程抢先获取
- 公平锁:如果同时还有另一个线程进来尝试获取,当它发现自己不是在队首的话,就会排到队尾,由队首的线程获取到锁
Synchronized和Lock区别
- Synchronized是一个关键字,Lock是一个类
- Synchronized无法判断获取锁的状态,Lock可以
- Synchronized会自动释放锁,Lock必须要手动释放
- Synchronized阻塞时会一直等,Lock可以放弃等待
- Synchronized可重入锁,不可以中断的,非公平。Lock可重入锁,可以判断锁,非公平(可以设置)
- Synchronized适合少量代码,Lock适合大量代码
ThreadLocal
Thread提供线程内部的局部变量,在本线程内随时随地可取,隔离其他线程。解决线程数据隔离的问题
ThreadLocal实现原理
每个Thread维护一个维护一个ThreadLocalMap映射表,这个映射表的key是ThreadLocal实例本身,value是真正需要存储的Object
代码示例
private static final ThreadLocal
@Override
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(new MyThread(i)).start();
}
}
static class MyThread implements Runnable {
private int index;
public MyThread(int index) {
this.index = index;
}
public void run() {
System.out.println("线程" + index + "的初始value:" + value.get());
for (int i = 0; i < 10; i++) {
value.set(value.get() + i);
}
System.out.println("线程" + index + "的累加value:" + value.get());
}
}
输出:
线程1的初始value:0
线程1的累加value:45
线程0的初始value:0
线程3的初始value:0
线程3的累加value:45
线程0的累加value:45
线程2的初始value:0
线程4的初始value:0
线程2的累加value:45
线程4的累加value:45
各个线程的value值是相互独立的,本线程的累加操作不会影响到其他线程的值,真正达到了线程内部隔离的效果。
多线程基础
wait和sleep区别
- 来自不同的类
wait => Object
sleep => Thread - 关于锁的释放
wait会释放锁,sleep不释放 - 使用的范围不同
wait必须在同步代码块中
sleep可以在任何地方 - 是否需要捕获异常
wait不需要捕获异常
sleep必须要捕获异常
Callable和Runnable
- Callable可以有返回值
- Callable可以抛出异常
- Callable的重写方法是call(),Runnable的重写方法是run()
- Runnable可以提交给Thread来包装下,直接启动一个线程来执行,而Callable则一般交给ExecuteService来执行
volatile
- 保证可见性,使一个线程所作的修改对其他线程可见
- 不保证原子性,可以使用原子类
java.util.concurrent.atomic.*来保证原子性 - 禁止指令重排,通过内存屏障,底层在操作前后加上内存屏障禁止上面指令和下面指令顺序交换
生产者和消费者问题
使用Synchronized
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
// 判断等待,业务,通知
class Data{ // 数字 资源类
private int number = 0;
//+1
public synchronized void increment() throws InterruptedException {
if (number!=0){ //0
// 等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知其他线程,我+1完毕了
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
if (number==0){ // 1
// 等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知其他线程,我-1完毕了
this.notifyAll();
}
}
当线程数较多时出现虚假唤醒现象,将if改为while判断解决
使用lock
public class B {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
// 判断等待,业务,通知
class Data2{ // 数字 资源类
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//condition.await(); // 等待
//condition.signalAll(); // 唤醒全部
//+1
public void increment() throws InterruptedException {
lock.lock();
try {
// 业务代码
while (number!=0){ //0
// 等待
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知其他线程,我+1完毕了
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//-1
public void decrement() throws InterruptedException {
lock.lock();
try {
while (number==0){ // 1
// 等待
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知其他线程,我-1完毕了
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
Condition 可以实现精准的通知和唤醒线程
/**
* A 执行完调用B,B执行完调用C,C执行完调用A
*/
public class C {
public static void main(String[] args) {
Data3 data = new Data3();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
data.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
data.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
data.printC();
}
},"C").start();
}
}
class Data3{ // 资源类 Lock
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1; // 1A 2B 3C
public void printA(){
lock.lock();
try {
// 业务,判断-> 执行-> 通知
while (number!=1){
// 等待
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"=>AAAAAAA");
// 唤醒,唤醒指定的人,B
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
// 业务,判断-> 执行-> 通知
while (number!=2){
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"=>BBBBBBBBB");
// 唤醒,唤醒指定的人,c
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
// 业务,判断-> 执行-> 通知
while (number!=3){
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"=>BBBBBBBBB");
// 唤醒,唤醒指定的人,c
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
集合类的并发
并发下产生 java.util.ConcurrentModificationException 并发修改异常
-
List
- List
list = new Vector<>(); - List
list = Collections.synchronizedList(new ArrayList<> ()); - List
list = new CopyOnWriteArrayList<>()
- List
-
Set
- Set
set = Collections.synchronizedSet(new HashSet<>()); - Set
set = new CopyOnWriteArraySet<>();
hashSet底层时HashMap,利用map的key是无法重复的,值为不变值
- Set
-
Map
Map<String, String> map = new ConcurrentHashMap<>();ConcurrentHashMap 在JDK 7之前是通过Lock和Segment(分段锁)实现并发安全,JDK 8之后改为CAS+synchronized来保证并发安全。
常用的辅助类CountDownLatch、CyclicBarrier、Semaphore
CountDownLatch
允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助(减法计数器)
countDownLatch.countDown();//数量-1
countDownLatch.await(); // 等待计数器归零,然后再向下执行
每次有线程调用 countDown() 数量-1,假设计数器变为0,countDownLatch.await() 就会被唤醒,继续 执行!
CyclicBarrier
允许一组线程全部等待彼此达到共同屏障点的同步辅助(加法计数器)
Semaphore 信号量
用来控制同时访问特定资源的线程数量,每次只允许一定数量的线程访问。可以用于做流量控制,特别公共资源有限的应用场景,比如数据库连接
Exchanger
用于线程间的数据交换,提供一个同步点,在这个同步点,两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。
锁
读写锁
读可以被多线程同时读,写的时候只能有一个线程去写。分为独占锁(写锁)和共享锁(读锁)。代码示例
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//写
readWriteLock.writeLock().lock();
try {
} catch () {
} finally {
readWriteLock.writeLock().unlock();
}
//读
readWriteLock.readLock().lock();
try {
} catch () {
} finally {
readWriteLock.readLock().unlock();
}
自旋锁
通过自旋锁可以减少线程阻塞造成的线程切换(包括挂起线程和恢复线程)。
- 当前线程竞争锁失败时,打算阻塞自己
- 不直接阻塞自己,而是自旋(空循环)一会
- 在自旋的同时重新竞争锁
- 如果自旋结束前获得了锁,那么锁获得成功;否则,自旋结束后阻塞自己
缺点:
- 单核处理器上,不存在实际的并行,当前线程不阻塞自己的话,旧owner就不能执行,锁永远不会释放,此时不管自旋多久都是浪费;进而,如果线程多而处理器少,自旋也会造成不少无谓的浪费。
- 自旋锁要占用CPU,如果是计算密集型任务,这一优化通常得不偿失,减少锁的使用是更好的选择。
- 如果锁竞争的时间比较长,那么自旋通常不能获得锁,白白浪费了自旋占用的CPU时间。这通常发生在锁持有时间长,且竞争激烈的场景中,此时应主动禁用自旋锁。
。
使用 -XX:-UseSpinning参数关闭自旋锁优化;-XX:PreBlockSpin参数修改默认的自旋次数
轻量级锁
轻量级锁的目标是,减少无实际竞争情况下,使用重量级锁产生的性能消耗,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。
轻量级锁是相对于重量级锁而言的。使用轻量级锁时,不需要申请互斥量,仅仅将Mark Word中的部分字节CAS更新指向线程栈中的Lock Record,如果更新成功,则轻量级锁获取成功,记录锁状态为轻量级锁;否则,说明已经有线程获得了轻量级锁,目前发生了锁竞争(不适合继续使用轻量级锁),接下来膨胀为重量级锁。
Mark Word是对象头的一部分;每个线程都拥有自己的线程栈(虚拟机栈),记录线程和函数调用的基本信息。二者属于JVM的基础内容
缺点:如果锁竞争激烈,那么轻量级将很快膨胀为重量级锁,那么维持轻量级锁的过程就成了浪费
偏向锁
减少无竞争且只有一个线程使用锁的情况下,使用轻量级锁产生的性能消耗。轻量级锁每次申请、释放锁都至少需要一次CAS,但偏向锁只有初始化时需要一次CAS。
在Mark Word中CAS记录owner(本质上也是更新,但初始值为空),如果记录成功,则偏向锁获取成功,记录锁状态为偏向锁,以后当前线程等于owner就可以零成本的直接获得锁;否则,说明有其他线程竞争,膨胀为轻量级锁。
缺点:如果明显存在其他线程申请锁,那么偏向锁将很快膨胀为轻量级锁。
使用参数 -XX:-UseBiasedLocking 禁止偏向锁优化(默认打开)
可重入锁
任意线程在获取到锁之后,再次获取该锁而不会被该锁所阻塞。
每个锁都关联了一个线程持有者和计数器。
线程再次获取锁:锁需要识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取;释放锁时计数器自减,当计数器为0时,锁释放成功。
其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁。
死锁
死锁解决办法
- 使用
jps -l定位进程号 - 使用
jstack 进程号找到死锁问题
阻塞队列 BlockingQueue
SynchronousQueue 同步队列 Deque双端队列
四组api
| 方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞等待 | 超时等待 |
|---|---|---|---|---|
| 添加 | add() | offer() | put() | offer(,,) |
| 移除 | remove() | poll() | take() | poll(,) |
| 检测队首元素 | element() | peek() | - | - |
SynchronousQueue同步队列:没有容量,进去一个元素,必须等待取出来之后,才能再往里面放一个元素
线程池
三大方法、七大参数、四种拒绝策略
线程池的好处:
- 降低资源的消耗
- 提高响应的速度
- 方便管理
三大方法
线程池不要使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险
Executors返回的线程池对象弊端:
- FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM
- CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM
三大方法:
- ExecutorService threadPool = Executors.newSingleThreadExecutor();// 单个线程
- ExecutorService threadPool = Executors.newFixedThreadPool(5); // 创建一个固定的线程池的大小
- ExecutorService threadPool = Executors.newCachedThreadPool(); // 可伸缩的,遇强则强,遇弱则弱
使用线程池创建线程
try {
threadPool.execute(()->{
//do something...
});
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程池用完,程序结束,关闭线程池
threadPool.shutdown();
}
七大参数
public ThreadPoolExecutor(int corePoolSize, // 核心线程池大小
int maximumPoolSize, // 最大核心线程池大小
long keepAliveTime, // 超时了没有人调用就会释放
TimeUnit unit, // 超时单位
BlockingQueue
ThreadFactory threadFactory, // 线程工厂:创建线程的,一般 不用动
RejectedExecutionHandler handle // 拒绝策略)
线程池任务的执行流程

自定义线程池
ExecutorService threadPool = new ThreadPoolExecutor(2, 5, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy());
try {
// 大承载:Deque + max
// 超过 RejectedExecutionException
for (int i = 1; i <= 9; i++) {
// 使用了线程池之后,使用线程池来创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 线程池用完,程序结束,关闭线程池
threadPool.shutdown();
}
四种拒绝策略
- new ThreadPoolExecutor.AbortPolicy() // 丢弃任务并抛出RejectedExecutionException异常,默认拒绝策略
- new ThreadPoolExecutor.CallerRunsPolicy() // 由调用线程(提交任务的线程)处理该任务
- new ThreadPoolExecutor.DiscardPolicy() // 丢弃任务不抛出异常
- new ThreadPoolExecutor.DiscardOldestPolicy() // 丢弃队列最前面的任务,然后重新提交被拒绝的任务,不会抛出异常!
线程池的最大线程的大小如何设置
- CPU密集型:和CPU核数保持一致,获取CPU核数
Runtime.getRuntime().availableProcessors() - IO密集型:判断程序中十分耗IO的线程
分支合并ForkJoin
并行执行任务,大数据量下提高效率
特点:工作窃取,当一个线程完成任务后拿取其他线程的任务来做,使用的是双端队列
/**
* 求和计算的任务!
* 普通方法 ForkJoin Stream并行流
* // 如何使用 forkjoin
* // 1、forkjoinPool 通过它来执行
* // 2、计算任务 forkjoinPool.execute(ForkJoinTask task)
* // 3. 计算类要继承 ForkJoinTask
*/
public class ForkJoinDemo extends RecursiveTask
private Long start; // 1
private Long end; // 1990900000
// 临界值
private Long temp = 10000L;
public ForkJoinDemo(Long start, Long end) {
this.start = start;
this.end = end;
}
// 计算方法
@Override
protected Long compute() {
if ((end-start)<temp){
Long sum = 0L;
for (Long i = start; i <= end; i++) {
sum += i;
}
return sum;
}else {
// forkjoin 递归
long middle = (start + end) / 2;
// 中间值
ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
task1.fork(); // 拆分任务,把任务压入线程队列
ForkJoinDemo task2 = new ForkJoinDemo(middle+1, end);
task2.fork(); // 拆分任务,把任务压入线程队列
return task1.join() + task2.join();
}
}
}
测试:
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// test1(); // 12224
// test2(); // 10038
// test3(); // 153
}
// 普通
public static void test1(){
Long sum = 0L;
long start = System.currentTimeMillis();
for (Long i = 1L; i <= 10_0000_0000; i++) {
sum += i;
}
long end = System.currentTimeMillis();
System.out.println("sum="+sum+" 时间:"+(end-start));
}
// 使用ForkJoin
public static void test2() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask
ForkJoinTask
Long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println("sum="+sum+" 时间:"+(end-start));
}
// Stream并行流
public static void test3(){
long start = System.currentTimeMillis();
long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println("sum="+"时间:"+(end-start));
}
}
参考:
浙公网安备 33010602011771号