juc并发编程学习总结
多线程详解_1 - 你的雷哥 - 博客园 (cnblogs.com)
多线程详解_2 - 你的雷哥 - 博客园 (cnblogs.com)
线程池详解_3 - 你的雷哥 - 博客园 (cnblogs.com)
多线程
创建线程的三种方式
继承Thread类,实现Runnable类,实现Callable类。
实现Callable也是创建线程池的一种方法
函数式编程
只有一个函数的接口,lambda是实现函数式编程的一种实现,形式简单,代码易懂。
多线程几种常用方法
1. public static Thread currentThread()
返回目前正在执行的线程
2. public final String getName()
返回线程的名称
3. public final int getPriority()
返回线程的优先级
4. public final void setPriority(String name)
设定线程名称
5. public final boolean isAlive()
判断线程是否在活动,如果是,返回true,否则返回false
6. public final void join()
等待这个线程死亡。
即调用该方法的线程强制执行,其它线程处于阻塞状态,该线程执行完毕后,其它线程再执行。
7. public static void sleep(long millis)
使用当前正在执行的线程休眠millis秒,线程处于阻塞状态
8. public static void yield()
当前正在执行的线程暂停一次,允许其他线程执行,不阻塞,线程进入就绪状态,如果没有其他等待执行的线程(或者这个线程又抢先抢占到cpu资源),则这个时候当前线程就会马上恢复执行。
9. public final void stop()
强迫线程停止执行。已过时。不推荐使用
sleep和yield不会释放锁资源,wait和join会释放锁资源。
线程状态
Java线程的三种停止方法
一、阻塞中断和非阻塞中断
interrupt() 方法并不会立即执行中断操作,这个方法只会给线程设置一个为true的中断标志,遇到wait,join,sleep会抛出异常,把中断标注重置为false,可以和return结合结束线程。
设置之后,则根据线程当前的状态进行不同的后续操作。
(1)如果,线程的当前状态处于非阻塞状态,那么仅仅是线程的中断标志被修改为true而已(2)如果线程的当前状态处于阻塞状态,那么在将中断标志设置为true后,如果是 wait、sleep以及join 三个方法引起的阻塞,那么会将线程的中断标志重新设置为false,并抛出一个InterruptedException ,这样受阻线程就得以退出阻塞的状态。
举例:一个线程在运行状态中,其中断标志被设置为true之后,一旦线程调用了wait、join、sleep方法中的一种,立马抛出一个InterruptedException,且中断标志被程序会自动清除,重新设置为false
总结:调用线程类的interrupted方法,其本质只是设置该线程的中断标志,将中断标志设置为true,并根据线程状态决定是否抛出异常。
public class TestThread1 { public static void main(String[] args) { MyRunnable1 myRunnable=new MyRunnable1(); Thread thread=new Thread(myRunnable,"子线程"); thread.start(); try{ //主线程休眠 Thread.sleep(3000); //调用中断,true thread.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } } } class MyRunnable1 implements Runnable{ @Override public void run() { int i=0; while(true){ System.out.println(Thread.currentThread().getName()+"循环第"+ ++i+"次"); try{ //判断线程的中断情况 boolean interruptStatus=Thread.currentThread().isInterrupted(); System.out.println(Thread.currentThread().getName()+"循环第"+ ++i+"次"+interruptStatus); Thread.sleep(1000); //非阻塞中断 只是设置标记位true //非阻塞中断 只是设置标记位true if(interruptStatus){ //如果中断为true则退出 break; } } catch (InterruptedException e) { // 一个线程在运行状态中,其中断标志被设置为true之后,一旦线程调用了 // wait、join、sleep方法中的一种,立马抛出一个InterruptedException,且中断标志被程序会自动清除,重新设置为false System.out.println("阻塞中断"+Thread.currentThread().isInterrupted());//显示false并抛异常 return;//不想返回还可继续写代码 } } } } 子线程循环第1次false 子线程循环第2次false 子线程循环第3次false 阻塞中断false
二、stop方法停止(废弃使用)
由于不安全,已经不使用了,因为stop会解除由线程获取的所有锁定,当在一个线程对象上调用stop()方法时,这个线程对象所运行的线程就会立即停止,假如一个线程正在执行:synchronized void { x = 3; y = 4;} 由于方法是同步的, 多个线程访问时总能保证x,y被同时赋值,而如果一个线程正在执行到x = 3;时,被调用了 stop()方法,即使在同步块中,它也会马上stop了,这样就产生了不完整的残废数据。
三、设置标记位停止(推荐的方法)
public class TestThread2_1 { public static void main(String[] args) throws InterruptedException { MyThreads my = new MyThreads(); new Thread(my, "线程A").start(); Thread.sleep(10000); //设置标记位 //my.setFlag(false); //stop方法 new Thread(my, "线程A").stop(); System.out.println("代码结束"); } } class MyThreads implements Runnable { private boolean flag = true; @Override public void run() { int i = 1; while (flag) { try { Thread.sleep(1000); System.out.println("第" + i + "次执行,线程名称为:" + Thread.currentThread().getName()); i++; } catch (InterruptedException e) { e.printStackTrace(); } } } public void setFlag(boolean flag) { this.flag = flag; } }
join和yield的使用
yield()方法使用示例
在下面的示例程序中,我随意的创建了名为生产者和消费者的两个线程。生产者设定为最小优先级,消费者设定为最高优先级。在Thread.yield()注释和非注释的情况下我将分别运行该程序。
没有调用yield()方法时,虽然输出有时改变,但是通常消费者行先打印出来,然后事生产者。
调用yield()方法时,两个线程依次打印,然后将执行机会交给对方,一直这样进行下去。
package com.itcast.demo; public class Producer extends Thread { public void run() { for (int i = 0; i < 5; i++) { System.out.println("I am Producer : Produced Item " + i); Thread.yield(); } } }
package com.itcast.demo; public class Producer extends Thread { public void run() { for (int i = 0; i < 5; i++) { System.out.println("I am Producer : Produced Item " + i); Thread.yield(); } } }
package com.itcast.demo; public class YieldExample { public static void main(String[] args) { Thread producer = new Producer(); Thread consumer = new Consumer(); producer.setPriority(Thread.MIN_PRIORITY); //Min Priority consumer.setPriority(Thread.MAX_PRIORITY); //Max Priority producer.start(); consumer.start(); } }
上述程序没调用yield()方法情况下的输出:
上述程序在调用yield()方法情况下的输出:
yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。
join()方法
线程实例的方法join()方法可以使得一个线程在另一个线程结束后再执行。如果join()方法在一个线程实例上调用,当前运行着的线程将阻塞直到这个线程实例完成了执行。
package com.itcast.demo2; public class ThreadImp implements Runnable { @Override public void run() { try { System.out.println("Begin ThreadImp"); Thread.sleep(5000);//休息5s System.out.println("End ThreadImp"); } catch (Exception e) { System.out.println(e); } } }
package com.itcast.demo2; public class JoinTest { public static void main(String[] args) { Thread t = new Thread(new ThreadImp()); t.start(); try { t.join(1000);//主程序等待t结束,只等1s if(t.isAlive()){ System.out.println("t has not finished"); }else{ System.out.println("t has finished"); } System.out.println("Joinfinished"); } catch (Exception e) { System.out.println(e); } } }
守护线程
同步代码块和同步方法
方式一:同步代码块: synchronized(同步监视器){ //操作共享数据的代码 } 注:1.同步监视器:俗称锁,任何一个类的对象都可以才充当锁。要想保证线程的安全,必须要求所有的线程共用同一把锁! 2.使用实现Runnable接口的方式创建多线程的话,同步代码块中的锁,可以考虑是this。如果使用继承Thread类的方式,慎用this! 3.共享数据:多个线程需要共同操作的变量。 明确哪部分是操作共享数据的代码。 方式二:同步方法:将操作共享数据的方法声明为synchronized。 比如:public synchronized void show(){ //操作共享数据的代码} 注:1.对于非静态的方法而言,使用同步的话,默认锁为:this。如果使用在继承的方式实现多线程的话,慎用! 2.对于静态的方法,如果使用同步,默认的锁为:当前类本身。以单例的懒汉式为例。 Class clazz = Singleton.class 总结:释放锁:wait(); 不释放锁: sleep() yield() suspend() (过时,可能导致死锁)
解释一下关于实现runnable接口为啥锁为this,而Thread类慎用,因为我们声明一个实现runnable对象就可以创建放入多个Thread里面,那么这些线程可以共享当前对象,所以是this就可以
但是继承Thread类,我们一般创建多个类对象,那么这些类对象的锁的this各不相同,不满足共享锁,所以不合适,此时可以考虑对方法声明为静态的,那么此时使用类本身作为锁便是共享的,因为对象可以是多个,但是类本身只有一个。
解决死锁的一个方法就是用完自己的锁后释放。
package Thread; /** * author liulei * data 5.25 * since 1.8 * version 1.0 * Description 死锁:多个线程互相抱着对方需要的资源,形成僵持 */ public class Test21 { public static void main(String[] args) { Makeup makeup = new Makeup(0,"李雷"); Makeup makeup1 = new Makeup(1,"韩梅梅"); makeup.start(); makeup1.start(); } } //口红 class Lipstick{ } //镜子 class Mirror{ } class Makeup extends Thread{ //需要的资源只有一份,用static来保证只有一份 static Lipstick lipstick = new Lipstick(); static Mirror mirror = new Mirror(); int choice;//选择 String girlName;//使用 public Makeup(int choice, String girlName) { this.choice = choice; this.girlName = girlName; } @Override public void run() { try { makeup(); } catch (InterruptedException e) { e.printStackTrace(); } } private void makeup() throws InterruptedException { if(choice == 0){//李雷获得口红的锁,韩梅梅获得镜子的锁,但李雷韩梅梅希望获取对方的锁,但又不愿意释放自己的锁,形成死锁 synchronized (lipstick){ System.out.println(this.girlName + "获得口红的锁"); Thread.sleep(100); synchronized (mirror){ System.out.println(this.girlName + "获得镜子的锁"); } } } if(choice == 1){ synchronized (mirror){ System.out.println(this.girlName + "获得镜子的锁"); Thread.sleep(100); synchronized (lipstick){ System.out.println(this.girlName + "获得口红的锁"); } } } } }
package Thread; /** * author liulei * data 5.25 * since 1.8 * version 1.0 * Description 解决死锁的一个方法就是用完自己的锁后释放 */ public class Test22 { public static void main(String[] args) { Makeup1 makeup = new Makeup1(0,"李雷"); Makeup1 makeup1 = new Makeup1(1,"韩梅梅"); makeup.start(); makeup1.start(); } } //口红 class Lipstick1{ } //镜子 class Mirror1{ } class Makeup1 extends Thread{ //需要的资源只有一份,用static来保证只有一份 static Lipstick lipstick = new Lipstick(); static Mirror mirror = new Mirror(); int choice;//选择 String girlName;//使用 public Makeup1(int choice, String girlName) { this.choice = choice; this.girlName = girlName; } @Override public void run() { try { makeup(); } catch (InterruptedException e) { e.printStackTrace(); } } private void makeup() throws InterruptedException { if(choice == 0){//李雷获得口红的锁,韩梅梅获得镜子的锁,但李雷韩梅梅希望获取对方的锁,但又不愿意释放自己的锁,形成死锁 synchronized (lipstick){ System.out.println(this.girlName + "获得口红的锁"); Thread.sleep(100); } synchronized (mirror){ System.out.println(this.girlName + "获得镜子的锁"); } } if(choice == 1){ synchronized (mirror){ System.out.println(this.girlName + "获得镜子的锁"); Thread.sleep(100); } synchronized (lipstick){ System.out.println(this.girlName + "获得口红的锁"); } } } }
Lock锁
对比
java中的notify和notifyAll有什么区别?
先说两个概念:锁池和等待池
- 锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
- 等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池中
Reference:java中的锁池和等待池
然后再来说notify和notifyAll的区别
- 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
- 当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争
- 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
Reference:线程间协作:wait、notify、notifyAll
综上,所谓唤醒线程,另一种解释可以说是将线程由等待池移动到锁池,notifyAll调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而notify只会唤醒一个线程。
有了这些理论基础,后面的notify可能会导致死锁,而notifyAll则不会的例子也就好解释了
Java多线程:newCondition()方法
关键字synchronized与wait()/notify()这两个方法一起使用可以实现等待/通知模式, Lock锁的newContition()方法返回Condition对象,Condition类也可以实现等待/通知模式。
用notify()通知时,JVM会随机唤醒condition对象某个等待的线程, 使用Condition类可以进行选择性通知, Condition比较常用的两个方法:
● await()会使当前线程等待,同时会释放锁,当其他线程调用signal()时,线程会重新获得锁并继续执行。
● signal()用于唤醒一个等待的线程。
注意:在调用Condition的await()/signal()方法前,也需要线程持有相关的Lock锁,调用await()后线程会释放这个锁,在singal()调用后会从当前Condition对象的等待队列中,唤醒 一个线程,唤醒的线程尝试获得锁, 一旦获得锁成功就继续执行。
Condition就是实现了管程里面的条件变量。 Java 语言内置的管程里只有一个条件变量,而Lock&Condition实现的管程支持多个条件变量。 因为支持多个条件变量,能让代码可读性更好,实现也更容易。 例如,你看我这里实现一个阻塞队列,就需要两个条件变量。
可爱的学妹,又真诚发问到:那如何利用两个条件变量实现阻塞队列呢?
一个阻塞队列,需要两个条件变量:
- 队列不空(空队列不可出队)
- 队列不满(队列已满不可入队)
condition最大的好处是选择指定的锁唤醒,避免了notify/notifyAll出现的唤醒无关的锁的问题,下面链接是一个例子
Java基础多线程(七)Condition_Coder Wang-CSDN博客
实现指定对象的唤醒。
package com.wkcto.lock.condition; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Condition等待与通知 */ public class Test01 { //定义锁 static Lock lock = new ReentrantLock(); //获得Condtion对象 static Condition condition = lock.newCondition(); //定义线程子类 static class SubThread extends Thread{ @Override public void run() { try { lock.lock(); //在调用await()前必须先获得锁 System.out.println("method lock"); condition.await(); //等待 System.out.println("method await"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); //释放锁 System.out.println("method unlock"); } } } public static void main(String[] args) throws InterruptedException { SubThread t = new SubThread(); t.start(); //子线程启动后,会转入等待状态 Thread.sleep(3000); //主线程在睡眠3秒后,唤醒子线程的等待 try { lock.lock(); condition.signal(); } finally { lock.unlock(); } } }
package com.wkcto.lock.condition; import java.io.PipedOutputStream; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** * 多个Condition实现通知部分线程, 使用更灵活 */ public class Test02 { static class Service{ private ReentrantLock lock = new ReentrantLock(); //定义锁对象 //定义两个Condtion对象 private Condition conditionA = lock.newCondition(); private Condition conditionB = lock.newCondition(); //定义方法,使用conditionA等待 public void waitMethodA(){ try { lock.lock(); System.out.println(Thread.currentThread().getName() + " begin wait:" + System.currentTimeMillis()); conditionA.await(); //等待 System.out.println(Thread.currentThread().getName() + " end wait:" + System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } //定义方法,使用conditionB等待 public void waitMethodB(){ try { lock.lock(); System.out.println(Thread.currentThread().getName() + " begin wait:" + System.currentTimeMillis()); conditionB.await(); //等待 System.out.println(Thread.currentThread().getName() + " end wait:" + System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } //定义方法唤醒conditionA对象上的等待 public void signalA(){ try { lock.lock(); System.out.println(Thread.currentThread().getName() + " sigal A time = " + System.currentTimeMillis()); conditionA.signal(); System.out.println(Thread.currentThread().getName() + " sigal A time = " + System.currentTimeMillis()); } finally { lock.unlock(); } } //定义方法唤醒conditionB对象上的等待 public void signalB(){ try { lock.lock(); System.out.println(Thread.currentThread().getName() + " sigal A time = " + System.currentTimeMillis()); conditionB.signal(); System.out.println(Thread.currentThread().getName() + " sigal A time = " + System.currentTimeMillis()); } finally { lock.unlock(); } } } public static void main(String[] args) throws InterruptedException { Service service = new Service(); //开启两个线程,分别调用waitMethodA(),waitMethodB()方法 new Thread(new Runnable() { @Override public void run() { service.waitMethodA(); } }).start(); new Thread(new Runnable() { @Override public void run() { service.waitMethodB(); } }).start(); Thread.sleep(3000); //main线程睡眠3秒 // service.signalA(); //唤醒 conditionA对象上的等待,conditionB上的等待依然继续等待 service.signalB(); } }
package com.wkcto.lock.condition; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 使用Condition实现生产者/消费者设计模式, 两个 线程交替打印 */ public class Test03 { static class MyService{ private Lock lock = new ReentrantLock(); //创建锁对象 private Condition condition = lock.newCondition(); //创建Condition对象 private boolean flag = true; //定义交替打印标志 //定义方法只打印----横线 public void printOne(){ try { lock.lock(); //锁定 while (flag){ //当flag为true等待 System.out.println(Thread.currentThread().getName() + " waiting..."); condition.await(); } //flag为false时打印 System.out.println(Thread.currentThread().getName() + " ---------------- "); flag = true; //修改交替打印标志 condition.signal(); //通知另外的线程打印 } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); //释放锁对象 } } //定义方法只打印***横线 public void printTwo(){ try { lock.lock(); //锁定 while (!flag){ //当flag为false等待 System.out.println(Thread.currentThread().getName() + " waiting..."); condition.await(); } //flag为true时打印 System.out.println(Thread.currentThread().getName() + " ****** "); flag = false; //修改交替打印标志 condition.signal(); //通知另外的线程打印 } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); //释放锁对象 } } } public static void main(String[] args) { MyService myService = new MyService(); //创建线程打印-- new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100; i++) { myService.printOne(); } } }).start(); //创建线程打印** new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100; i++) { myService.printTwo(); } } }).start(); } }
老是强调多线程,那么 Java真的可以开启线程吗?
public synchronized void start() { if (threadStatus != 0) throw new IllegalThreadStateException(); group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { } } } // 本地方法,底层的C++ ,Java 无法直接操作硬件 private native void start0();
Synchronized 和 Lock 区别
ReentrantLock中lock(),tryLock(),lockInterruptibly()的区别
对于多个线程竞争资源的问题会出现notifyAll把不该唤醒的线程唤醒,出现虚假唤醒问题,
使用while循环代替if判断,使用lock和condition的await和signal代替synchronized
4.使用condition实现精准唤醒线程
public class PC_WithAwakeByCondition { public static void main(String[] args) { Data_AwakeInOrder data = new Data_AwakeInOrder(); 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 Data_AwakeInOrder{ private Lock lock = new ReentrantLock(); private Condition condition1 = lock.newCondition(); private Condition condition2 = lock.newCondition(); private Condition condition3 = lock.newCondition(); private int number = 1; public void printA(){ lock.lock(); try { // 业务,判断-> 执行-> 通知 while (number != 1) { // 等待 condition1.await(); } System.out.println(Thread.currentThread().getName() + "=>AAAAAA"+"-----number为->"+number); // 唤醒,唤醒指定的人,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() + "=>BBBBBB"+"-----number为->"+number); // 唤醒,唤醒指定的人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() + "=>CCCCCC"+"-----number为->"+number); // 唤醒,唤醒指定的人A number = 1; condition1.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
5.使用wait()与notify()实现精准唤醒
public class PC_AwakeWithWait { public static void main(String[] args) { Data_AwakeInOrderByWait data = new Data_AwakeInOrderByWait(); new Thread(()->{ for (int i = 0; i < 100; i++) { data.printA(); } },"T_A").start(); new Thread(()->{ for (int i = 0; i < 100; i++) { data.printB(); } },"T_B").start(); new Thread(()->{ for (int i = 0; i < 100; i++) { data.printC(); } },"T_C").start(); } } //资源类 class Data_AwakeInOrderByWait{ private int number = 1; public synchronized void printA() { // 业务,判断-> 执行-> 通知 while (number != 1) { // 等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + "=>AAAAAA->"+number); // 唤醒,唤醒指定的人,B number = 2; this.notifyAll(); } public synchronized void printB(){ // 业务,判断-> 执行-> 通知 while (number != 2) { // 等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + "=>BBBBBB->"+number); // 唤醒,唤醒指定的人,B number = 3; this.notifyAll(); } public synchronized void printC(){ // 业务,判断-> 执行-> 通知 while (number != 3) { // 等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + "=>CCCCCC->"+number); // 唤醒,唤醒指定的人,B number = 1; this.notifyAll(); } }
普通方法执行速度快于加锁方法。静态方法锁为当前类,非静态方法锁为当前对象,注意区别。
CopyOnWriteArray***介绍
CopyOnWriteArrayList这是一个ArrayList的线程安全的变体,其原理大概可以通俗的理解为:初始化的时候只有一个容器,很长一段时间,这个容器数据、数量等没有发生变化的时候,大家(多个线程),都是读取(假设这段时间里只发生读取的操作)同一个容器中的数据,所以这样大家读到的数据都是唯一、一致、安全的,但是后来有人往里面增加了一个数据,这个时候CopyOnWriteArrayList 底层实现添加的原理是先copy出一个容器(可以简称副本),再往新的容器里添加这个新的数据,最后把新的容器的引用地址赋值给了之前那个旧的的容器地址,但是在添加这个数据的期间,其他线程如果要去读取数据,仍然是读取到旧的容器里的数据。
Callable接口
一是扔给futuretask,再给Thread
二是扔给executeservice对象在线程池里面运行。
8.1 CountDownLatch
该对象在多个线程里面调用countdown方法指定次数后会唤醒该对象的await,常用来做计数器。
顾名思义:倒计时锁存器
不管你线程中间执行的情况,结果若是线程执行完了,那就再执行最后语句,如果没达到条件就一直等
(领导:不看过程只看结果)
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// 总数是6,必须要执行任务的时候,再使用!
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <=6 ; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" Go out");
countDownLatch.countDown(); // 数量-1
},String.valueOf(i)).start();
}
countDownLatch.await(); // 等待计数器归零,然后再向下执行
System.out.println("Close Door");
}
}
1 Go out
5 Go out
4 Go out
3 Go out
2 Go out
6 Go out
Close Door
如果创建了7条任务线程,但只countDown了6次,那么将会一直阻塞线程
总结:
CountDownLatch countDownLatch = new CountDownLatch(6); 创建线程总数
countDownLatch.countDown(); 实行完线程数-1
countDownLatch.await(); 等待计数器归零,然后再向下执行
每次有线程调用 countDown() 数量-1,假设计数器变为0,countDownLatch.await() 就会被唤醒,继续执行
CyclicBarrier
cyclicbarrier执行await指定次数后进入屏障,执行barrierAction线程、
见名之意:循环障碍,与8.1 方法相反的,加法计时器
public class CyclicBarrierDemo {
public static void main(String[] args) {
/**
* 集齐7颗龙珠召唤神龙
*/
// 召唤龙珠的线程:7条线程
// 创建成功后执行runnable接口打印“召唤七颗龙珠成功”
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("召唤七颗龙珠成功");
});
for (int i = 1; i <= 7; i++) {
final int temp = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "收集到第" + temp + "个龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e){
e.printStackTrace();
}
}).start();
}
}
}
创建线程Barrier后开启线程执行:
CyclicBarrier(int parties, Runnable barrierAction)
创建一个新的 CyclicBarrier ,当给定数量的线程(线程)等待时,它将跳闸,当屏障跳闸时执行给定的屏障动作,由最后一个进入屏障的线程执行。
并在调用await()方法时自动+1:
如果执行的线程数到达设定值,则会执行 创建时设定的屏障动作,
如果无法到达则线程会处在阻塞状态
Semaphore
见名之意:信号量,可近似看作资源池。
举例子(抢车位):
public class SemaphoreDemo { public static void main(String[] args) { // 线程数量:停车位! 限流! //创建只有3个停车位的停车场 Semaphore semaphore = new Semaphore(3); for (int i = 1; i <=6 ; i++) { new Thread(()->{ // acquire() 得到 try { //抢车位。。。 semaphore.acquire(); System.out.println(Thread.currentThread().getName()+"抢到车位👍"); TimeUnit.SECONDS.sleep(2); System.out.println(Thread.currentThread().getName()+"离开车位😀"); } catch (InterruptedException e) { e.printStackTrace(); } finally {、 //停车结束,离开车位 semaphore.release(); // release() 释放 } },String.valueOf(i)+"号车-->").start(); } } }
读写锁
ReadWriteLock 接口
所有已知实现类: ReentrantReadWriteLock
读:可多条线程同时获取数据
写:只能单条线程写入
独占锁/排它锁/写锁 共享锁/读锁
public class TestReadWriteLock { public static void main(String[] args) { //未上锁: // MyCache myCache = new MyCache(); //上了读写锁: MyCacheWithLock myCache = new MyCacheWithLock(); //写入: for (int i = 1; i <= 5; i++) { final int temp = i; new Thread(() -> { myCache.write(temp + "", temp + ""); }, String.valueOf(i)).start(); } for (int i = 1; i <= 5; i++) { final int temp = i; new Thread(() -> { myCache.read(temp + ""); }, String.valueOf(i)).start(); } } } class MyCacheWithLock { private volatile Map<String, Object> map = new HashMap<>(); //读写锁:对数据更精准控制 private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private Lock lock = new ReentrantLock(); //写数据:只希望有一个线程在执行 public void write(String key, Object value) { readWriteLock.writeLock().lock(); try { System.out.println(Thread.currentThread().getName() + "写入" + key); map.put(key, value); System.out.println(Thread.currentThread().getName() + "写入完成!"); } catch (Exception e) { e.printStackTrace(); } finally { readWriteLock.writeLock().unlock(); } } //读数据:可一条或者多条同时执行 public void read(String key) { readWriteLock.readLock().lock(); try { System.out.println(Thread.currentThread().getName() + "读取数据:" + key); Object o = map.get(key); System.out.println(Thread.currentThread().getName() + "读取数据完成-->" + o); } catch (Exception e) { e.printStackTrace(); } finally { readWriteLock.readLock().unlock(); } } /** * 存入数据过程上锁,安全 */ } /** * 未上锁: */ class MyCache { private volatile Map<String, Object> map = new HashMap<>(); //写数据: public void write(String key, Object value) { System.out.println(Thread.currentThread().getName() + "写入" + key); map.put(key, value); System.out.println(Thread.currentThread().getName() + "写入完成!"); } //读数据: public void read(String key) { System.out.println(Thread.currentThread().getName() + "读取数据:" + key); Object o = map.get(key); System.out.println(Thread.currentThread().getName() + "读取数据完成-->" + o); } /** * 运行结果: * 写入线程会被 读取线程中断,造成脏读,对数据不安全 */ }
SynchronousQueue 同步队列
没有容量==> 进去一个元素,必须等待取出来之后,才能再往里面放一个元素!
和其他的BlockingQueue 不一样, SynchronousQueue 不存储元素,put了一个元素,必须从里面先take取出来,否则不能再put进去值!
Executors 工具类中3大方法(详见API)
public static ExecutorService newSingleThreadExecutor() //创建一个线程池,根据需要创建新的线程,但在可用时将重用先前构建的线程。
public static ExecutorService newFixedThreadPool(int nThreads) //创建一个线程池,使用固定数量的线程操作了共享无界队列
public static ExecutorService newCachedThreadPool() //创建一个线程池,根据需要创建新的线程,但在可用时将重用先前构建的线程。
定时线程池(newScheduledThreadPool)
其本质都是调用本质ThreadPoolExecutor
创建线程池,也是 阿里巴巴规范中提及的方法:
public ThreadPoolExecutor(int corePoolSize, //核心线程池大小
int maximumPoolSize, //最大核心线程池大小
long keepAliveTime, //超时了没有人调用就会释放
TimeUnit unit, //超时单位
BlockingQueue<Runnable> workQueue,//阻塞队列
ThreadFactory threadFactory,// 线程工厂:创建线程的,一般不用动
RejectedExecutionHandler handler/*拒绝策略*/) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
四大函数式接口
Function 函数型接口
Predicate 断定型接口
Suppier 供给型接口
Consummer 消费型接口
Stream 流式计算
把计算在一个流里面来解决。
/** * Description: * 题目要求: 用一行代码实现 * 1. Id 必须是偶数 * 2.年龄必须大于23 * 3. 用户名转为大写 * 4. 用户名倒序 * 5. 只能输出一个用户 * * @author jiaoqianjin * Date: 2020/8/12 14:55 **/ public class StreamDemo { public static void main(String[] args) { User u1 = new User(1, "a", 23); User u2 = new User(2, "b", 23); User u3 = new User(3, "c", 23); User u4 = new User(6, "d", 24); User u5 = new User(4, "e", 25); List<User> list = Arrays.asList(u1, u2, u3, u4, u5); // lambda、链式编程、函数式接口、流式计算 list.stream() //使用四大函数式接口在一个流里面完成一行代码满足要求。 .filter(user -> {return user.getId()%2 == 0;}) .filter(user -> {return user.getAge() > 23;}) .map(user -> {return user.getName().toUpperCase();}) .sorted((user1, user2) -> {return user2.compareTo(user1);}) .limit(1) .forEach(System.out::println); } }
ForkJoin
ForkJoin 在JDK1.7,并行执行任务!提高效率~。在大数据量速率会更快!
大数据中:MapReduce 核心思想->把大任务拆分为小任务!
ForkJoin 特点: 工作窃取!
实现原理是:双端队列!从上面和下面都可以去拿到任务进行执行!
如何使用ForkJoin?
-
1、通过ForkJoinPool来执行
-
2、计算任务 execute(ForkJoinTask<?> task)
-
3、计算类要去继承ForkJoinTask;
ForkJoin 的计算类
package com.marchsoft.forkjoin;import java.util.concurrent.RecursiveTask;/** * Description: * * @author jiaoqianjin * Date: 2020/8/13 8:33 **/publicclassForkJoinDemoextendsRecursiveTask<Long>{privatelong star;privatelongend;/** 临界值 */privatelong temp =1000000L;publicForkJoinDemo(long star,longend){this.star = star;this.end=end;}/** * 计算方法 * @return*/@OverrideprotectedLong compute(){if((end- star)< temp){Long sum =0L;for(Long i = star; i <end; i++){ sum += i;}return sum;}else{// 使用ForkJoin 分而治之 计算//1 . 计算平均值long middle =(star +end)/2;ForkJoinDemo forkJoinDemo1 =newForkJoinDemo(star, middle);// 拆分任务,把线程压入线程队列 forkJoinDemo1.fork();ForkJoinDemo forkJoinDemo2 =newForkJoinDemo(middle,end); forkJoinDemo2.fork();long taskSum = forkJoinDemo1.join()+ forkJoinDemo2.join();return taskSum;}}}
测试类
package com.marchsoft.forkjoin;import java.util.concurrent.ExecutionException;import java.util.concurrent.ForkJoinPool;import java.util.concurrent.ForkJoinTask;import java.util.stream.LongStream;/** * Description: * * @author jiaoqianjin * Date: 2020/8/13 8:43 **/publicclassForkJoinTest{privatestaticfinallong SUM =20_0000_0000;publicstaticvoid main(String[] args)throwsExecutionException,InterruptedException{ test1(); test2(); test3();}/** * 使用普通方法 */publicstaticvoid test1(){long star =System.currentTimeMillis();long sum =0L;for(long i =1; i < SUM ; i++){ sum += i;}longend=System.currentTimeMillis();System.out.println(sum);System.out.println("时间:"+(end- star));System.out.println("----------------------");}/** * 使用ForkJoin 方法 */publicstaticvoid test2()throwsExecutionException,InterruptedException{long star =System.currentTimeMillis();ForkJoinPool forkJoinPool =newForkJoinPool();ForkJoinTask<Long> task =newForkJoinDemo(0L, SUM);ForkJoinTask<Long> submit = forkJoinPool.submit(task);Long along = submit.get();System.out.println(along);longend=System.currentTimeMillis();System.out.println("时间:"+(end- star));System.out.println("-----------");}/** * 使用 Stream 流计算 */publicstaticvoid test3(){long star =System.currentTimeMillis();long sum =LongStream.range(0L,20_0000_0000L).parallel().reduce(0,Long::sum);System.out.println(sum);longend=System.currentTimeMillis();System.out.println("时间:"+(end- star));System.out.println("-----------");}}
异步回调
Future 设计的初衷:对将来的某个事件结果进行建模!
其实就是前端 --> 发送ajax异步请求给后端
JMM
1)对Volatile 的理解
Volatile 是 Java 虚拟机提供 轻量级的同步机制
1、保证可见性
2、不保证原子性
3、禁止指令重排
如何实现可见性
volatile变量修饰的共享变量在进行写操作的时候回多出一行汇编:
0x01a3de1d:movb 0×0,0×1104800(0×0,(%esp);
Lock前缀的指令在多核处理器下会引发两件事情。
1)将当前处理器缓存行的数据写回到系统内存。
2)这个写回内存的操作会使其他cpu里缓存了该内存地址的数据无效。
多处理器总线嗅探:
为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存后再进行操作,但操作不知道何时会写到内存。如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己的缓存值是不是过期了,如果处理器发现自己缓存行对应的内存地址呗修改,就会将当前处理器的缓存行设置无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据库读到处理器缓存中。
2)什么是JMM?
JMM:JAVA内存模型,不存在的东西,是一个概念,也是一个约定!
关于JMM的一些同步的约定:
1、线程解锁前,必须把共享变量立刻刷回主存;
2、线程加锁前,必须读取主存中的最新值到工作内存中;
3、加锁和解锁是同一把锁;
线程中分为 工作内存、主内存
8种操作:
Read(读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用;
load(载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中;
Use(使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令;
assign(赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中;
store(存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用;
write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中;
lock(锁定):作用于主内存的变量,把一个变量标识为线程独占状态;
unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;
- volatile可以保证可见性;
- 不能保证原子性
- 由于内存屏障,可以保证避免指令重排的现象产生
对于一个变量除了要加volatile之外,要解决不能保证原子性的问题,可以通过使用synchronized或者lock锁或者使用原子数据类型来解决,最终保证数据在并发场景的安全性。
赋值操作和取值操作是原子操作
赋值操作和取值操作对于变量是原子操作,注意y=x;包含了取值x和赋值两种操作,所以不是原子操作,要加volatile
join是让调用join方法的线程优先执行完毕再继续执行主线程,yield是释放资源重新参与竞争。
局部变量
局部变量是存储在栈上的,而栈上的内容在当前线程执行完成之后就会被GC回收掉。
lambda表达式
lambda表达式最终被处理为一个额外的线程去执行。绝对不是上面提到的线程。如果上面的线程执行完了,而这个线程又使用到了上面提到的局部变量会出现错误
为什么 Lambda 表达式(匿名类) 不能访问非 final 的局部变量呢?因为实例变量存在堆中,而局部变量是在栈上分配,Lambda 表达(匿名类) 会在另一个线程中执行。如果在线程中要直接访问一个局部变量,可能线程执行时该局部变量已经被销毁了,而 final 类型的局部变量在 Lambda 表达式(匿名类) 中其实是局部变量的一个拷贝。
原始构成:
synchronized是关键字属于JVM层面,monitorenter(底层是通过monitor对象来完成,其实waittify等方法也依赖于monitor对象只有在同步块或者方法中才能调用waittify)
lock是类,是api层面的锁
使用方法:
synchronized不需要手动释放锁,当synchronized代码执行完以后系统会自动让线程释放对锁的占用,
ReentrantLock则需要手动释放锁,不然可能会导致死锁现象。
是否可中断
synchronized不可中断
ReentrantLock可以中断,也可以不中断,中断的话要调用Interrupt
加锁是否公平
synchronized非公平锁
ReentrantLock两者都可以,默认公平锁。主要看构造方法的boolean值。
绑定多个condition
synchronized没有
ReentrantLock用来实现分组唤醒需要的线程们,可以精确唤醒,而不是像synchronized要么随机唤醒一个或者全部唤醒。
什么是CAS :
CAS 是CompareAndSwap的简称,从字面上理解就是比较并更新,简单来说:从某一内存上取值V,和预期值A进行比较,
如果内存值V和预期值A的结果相等,那么我们就把新值B更新到内存,如果不相等,那么就重复上述操作直到成功为止。
CAS锁其又称作Java的乐观锁,但是我们可以看到其实质上是没有上🔒的,只是赋值的过程前多了一个比较的方法,因此可能引起一定的ABA问题:
[狸猫换太子] 如果另一个线程修改V值假设原来是A,先修改成B,再修改回成A。当前线程的CAS操作无法分辨当前V值是否发生过变化。
解决上述问题的一个策略是:
每一次倒水假设有一个自动记录仪记录下,也就是给操作赋值上一个标致,这样主人回来就可以分辨在他离开后是否发生过重新倒满的情况。
这也是解决ABA问题目前采用的策略,这就需要我们的原子引用
原子引用
官方说明:
一个AtomicStampedReference维护对象引用以及整数“印记”,可以原子更新。
实现注意事项:此实现通过创建表示“boxed”[引用,整数]对的内部对象来维护加盖引用
- 公平锁(先到先得):是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到。
- 非公平锁(直接竞争):是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发的情况下,有可能会造成优先级反转或者饥饿现象。(线程尝试获取锁,如果获取不到则进入队列,可以的话直接获取锁,否则从队列的首个线程获取锁)
死锁
互斥条件:一个资源每次只能被一个进程使用;请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放;不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺;循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
内存屏障
1、什么是内存屏障
内存屏障其实就是一个CPU指令,在硬件层面上来说可以扥为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。主要有两个作用:
(1)阻止屏障两侧的指令重排序;
(2)强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。
在JVM层面上来说作用与上面的一样,但是种类可以分为四种:
2、volatile如何保证有序性?
首先一个变量被volatile关键字修饰之后有两个作用:
(1)对于写操作:对变量更改完之后,要立刻写回到主存中。
(2)对于读操作:对变量读取的时候,要从主存中读,而不是缓存。
OK,现在针对上面JVM的四种内存屏障,应用到volatile身上。因此volatile也带有了这种效果。其实上面提到的这些内存屏障应用的效果,可以happen-before来总结归纳。
3、内存屏障分类
内存屏障有三种类型和一种伪类型:
(1)lfence:即读屏障(Load Barrier),在读指令前插入读屏障,可以让高速缓存中的数据失效,重新从主内存加载数据,以保证读取的是最新的数据。
(2)sfence:即写屏障(Store Barrier),在写指令之后插入写屏障,能让写入缓存的最新数据写回到主内存,以保证写入的数据立刻对其他线程可见。
(3)mfence,即全能屏障,具备ifence和sfence的能力。
(4)Lock前缀:Lock不是一种内存屏障,但是它能完成类似全能型内存屏障的功能。
为什么说Lock是一种伪类型的内存屏障,是因为内存屏障具有happen-before的效果,而Lock在一定程度上保证了先后执行的顺序,因此也叫做伪类型。比如,IO操作的指令,当指令不执行时,就具有了mfence的功能。
指令重排的原理是为了提升CPU多段流水线的效率
lock()
两个线程都使用lock获取锁,如果线程A获取到了锁,线程B只能等待,对线程B调用interrupt()方法不能中断线程B的等待过程。
tryLock()
使用lock获取锁,如果线程A获取到了锁,线程A返回true,线程B直接返回false。可以传入时间参数,表示拿不到锁等待一段时间,这段时间内还是拿不到就返回false。
lockInterruptibly()
两个线程都使用lockInterruptibly获取锁,如果线程A获取到了锁,线程B只能等待,对线程B调用interrupt()方法能够中断线程B的等待过程。
- 公平锁:是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到。
- 非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发的情况下,有可能会造成优先级反转或者饥饿现象
lock和synchronized的区别
如何使用ForkJoin?
-
1、通过ForkJoinPool来执行
-
2、计算任务 execute(ForkJoinTask<?> task)
-
3、计算类要去继承ForkJoinTask;
ForkJoin 的计算类
package com.marchsoft.forkjoin;import java.util.concurrent.RecursiveTask;/** * Description: * * @author jiaoqianjin * Date: 2020/8/13 8:33 **/publicclassForkJoinDemoextendsRecursiveTask<Long>{privatelong star;privatelongend;/** 临界值 */privatelong temp =1000000L;publicForkJoinDemo(long star,longend){this.star = star;this.end=end;}/** * 计算方法 * @return*/@OverrideprotectedLong compute(){if((end- star)< temp){Long sum =0L;for(Long i = star; i <end; i++){ sum += i;}return sum;}else{// 使用ForkJoin 分而治之 计算//1 . 计算平均值long middle =(star +end)/2;ForkJoinDemo forkJoinDemo1 =newForkJoinDemo(star, middle);// 拆分任务,把线程压入线程队列 forkJoinDemo1.fork();ForkJoinDemo forkJoinDemo2 =newForkJoinDemo(middle,end); forkJoinDemo2.fork();long taskSum = forkJoinDemo1.join()+ forkJoinDemo2.join();return taskSum;}}}
测试类
package com.marchsoft.forkjoin;import java.util.concurrent.ExecutionException;import java.util.concurrent.ForkJoinPool;import java.util.concurrent.ForkJoinTask;import java.util.stream.LongStream;/** * Description: * * @author jiaoqianjin * Date: 2020/8/13 8:43 **/publicclassForkJoinTest{privatestaticfinallong SUM =20_0000_0000;publicstaticvoid main(String[] args)throwsExecutionException,InterruptedException{ test1(); test2(); test3();}/** * 使用普通方法 */publicstaticvoid test1(){long star =System.currentTimeMillis();long sum =0L;for(long i =1; i < SUM ; i++){ sum += i;}longend=System.currentTimeMillis();System.out.println(sum);System.out.println("时间:"+(end- star));System.out.println("----------------------");}/** * 使用ForkJoin 方法 */publicstaticvoid test2()throwsExecutionException,InterruptedException{long star =System.currentTimeMillis();ForkJoinPool forkJoinPool =newForkJoinPool();ForkJoinTask<Long> task =newForkJoinDemo(0L, SUM);ForkJoinTask<Long> submit = forkJoinPool.submit(task);Long along = submit.get();System.out.println(along);longend=System.currentTimeMillis();System.out.println("时间:"+(end- star));System.out.println("-----------");}/** * 使用 Stream 流计算 */publicstaticvoid test3(){long star =System.currentTimeMillis();long sum =LongStream.range(0L,20_0000_0000L).parallel().reduce(0,Long::sum);System.out.println(sum);longend=System.currentTimeMillis();System.out.println("时间:"+(end- star));System.out.println("-----------");}}