一. JUC概述
-
什么是JUC?
(1)java并发编程包中的一些工具类,这些工具类可以更加方便实现并发编程操作。
(2)JUC 就是 java.util .concurrent 工具包的简称。这是一个处理线程的工具包,JDK 1.5 开始出现的。
-
线程和进程的区别?
进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程— —资源分配的最小单位。
线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个 单元执行流。线程——程序执行的最小单位。
-
并发和并行的概念区别?
并发:同一时刻多个线程在访问同一个资源,多个线程对一个点 例子:春运抢票 电商秒杀…
并行:多项工作一起执行,之后再汇总 例子:泡方便面,电水壶烧水,一边撕调料倒入桶中
-
什么是管程?
(1)Moniters,也称为监视器。在操作系统中叫监视器;在java中叫锁。
(2)是一种同步机制,保证同一个时间,只有一个线程能去访问被保护数据或者代码
(3)jvm同步基于进入和退出,使用管程对象实现的,在临界区加锁操作,这个过程通过管程对象进行管理。
==以上是老师的讲解,第三条根本听不懂,下面是博客园上的Java中的管程 - 被罚站的树 - 博客园 (cnblogs.com)=
(1)所谓管程,指的是管理共享变量以及对其操作过程,让它们支持并发访问。
(2)就是管理类的成员变量和成员方法,让这个类是线程安全的。
二. Lock接口
-
多线程编程步骤(上部。。也很无语。。。):
(1)创建资源类,在资源类创建属性和操作方法。
(2)创建多个线程,调用资源类的操作方法。
-
Synchronized实现卖票例子(我感觉这段代码非常牛,活用了匿名内部类,也可修改为lambda表达式的形式)
package com.atguigu.sync; //第一步 创建资源类,定义属性和和操作方法 class Ticket { //票数 private int number = 30; //操作方法:卖票 public synchronized void sale() { //判断:是否有票 if(number > 0) { System.out.println(Thread.currentThread().getName()+" : 卖出:"+(number--)+" 剩下:"+number); } } } public class SaleTicket { //第二步 创建多个线程,调用资源类的操作方法 public static void main(String[] args) { //创建Ticket对象 Ticket ticket = new Ticket(); //创建三个线程 new Thread(new Runnable() { @Override public void run() { //调用卖票方法 for (int i = 0; i < 40; i++) { ticket.sale(); } } },"AA").start(); new Thread(new Runnable() { @Override public void run() { //调用卖票方法 for (int i = 0; i < 40; i++) { ticket.sale(); } } },"BB").start(); new Thread(new Runnable() { @Override public void run() { //调用卖票方法 for (int i = 0; i < 40; i++) { ticket.sale(); } } },"CC").start(); } }
-
什么是Lock接口?
Lock 锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允 许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对 象。Lock 提供了比 synchronized 更多的功能。
-
Lock 与的 Synchronized 区别
• Lock 不是 Java 语言内置的,synchronized 是 Java 语言的关键字,因此是内 置特性。Lock 是一个类,通过这个类可以实现同步访问;
• Lock 和 synchronized 有一点非常大的不同,采用 synchronized 不需要用户 去手动释放锁,当 synchronized 方法或synchronized 代码块执行完之后, 系统会自动让线程释放对锁的占用;而 Lock 则必须要用户去手动释放锁,如 果没有主动释放锁,就有可能导致出现死锁现象。
小结里的==
Lock 和 synchronized 有以下几点不同:
(1)Lock 是一个接口,而 synchronized 是 Java 中的关键字,synchronized 是内 置的语言实现;
(2)synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现 象发生;而 Lock 在发生异常时,如果没有主动 通过 unLock()去释放锁,则很 可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁;
(3)Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用 synchronized 时,等待的线程会一直等待下去,不能够响 应中断;
(4)通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
(5)Lock 可以提高多个线程进行读操作的效率。 在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源 非 常激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于 synchronized。
-
Lock锁代替Synchronized同步锁实现卖票例子
package com.atguigu.lock; import java.util.concurrent.locks.ReentrantLock; //第一步 创建资源类,定义属性和和操作方法 class LTicket { //票数量 private int number = 30; //创建可重入锁 private final ReentrantLock lock = new ReentrantLock(true); //卖票方法 public void sale() { //上锁 lock.lock(); try { //判断是否有票 if(number > 0) { System.out.println(Thread.currentThread().getName()+" :卖出"+(number--)+" 剩余:"+number); } } finally { //解锁 lock.unlock(); } } } public class LSaleTicket { //第二步 创建多个线程,调用资源类的操作方法 //创建三个线程 public static void main(String[] args) { LTicket ticket = new LTicket(); new Thread(()-> { for (int i = 0; i < 40; i++) { ticket.sale(); } },"AA").start(); new Thread(()-> { for (int i = 0; i < 40; i++) { ticket.sale(); } },"BB").start(); new Thread(()-> { for (int i = 0; i < 40; i++) { ticket.sale(); } },"CC").start(); } }
-
线程调用start()方法,是否马上创建线程?
不是,有可能马上创建,也有可能过一会在创建
start()方法调用了start0()方法,start0()方法有个关键字native,到这java代码就无能为力了,需要调用操作系统来完成,最终是由操作系统来决定的。看操作系统是否空闲。
三. 线程间通信及定制化通信
-
创建线程步骤(中部,下部):
下部是用while代替了if这里已经替换,
(1)第一步:创建资源类,在资源类创建属性和操作方法。
(2)第二步:在资源类操作方法中进行以下操作
1)判断
2)干活
3)通知
(3)创建多个线程,调用资源类的操作方法。
(4)第四步:用while代替if,防止虚假唤醒问题
-
Synchronized实现案例:
//第一步 创建资源类,定义属性和操作方法 class Share { //初始值 private int number = 0; //+1的方法 public synchronized void incr() throws InterruptedException { //第二步 判断 干活 通知 while(number != 0) { //判断number值是否是0,如果不是0,等待 this.wait(); //在哪里睡,就在哪里醒 } //如果number值是0,就+1操作 number++; System.out.println(Thread.currentThread().getName()+" :: "+number); //通知其他线程 this.notifyAll(); } //-1的方法 public synchronized void decr() throws InterruptedException { //判断 while(number != 1) { this.wait(); } //干活 number--; System.out.println(Thread.currentThread().getName()+" :: "+number); //通知其他线程 this.notifyAll(); } } public class ThreadDemo1 { //第三步 创建多个线程,调用资源类的操作方法 public static void main(String[] args) { Share share = new Share(); //创建线程 new Thread(()->{ for (int i = 1; i <=10; i++) { try { share.incr(); //+1 } catch (InterruptedException e) { e.printStackTrace(); } } },"AA").start(); new Thread(()->{ for (int i = 1; i <=10; i++) { try { share.decr(); //-1 } catch (InterruptedException e) { e.printStackTrace(); } } },"BB").start(); new Thread(()->{ for (int i = 1; i <=10; i++) { try { share.incr(); //+1 } catch (InterruptedException e) { e.printStackTrace(); } } },"CC").start(); new Thread(()->{ for (int i = 1; i <=10; i++) { try { share.decr(); //-1 } catch (InterruptedException e) { e.printStackTrace(); } } },"DD").start(); } }
思考一下,这个案例是不是和多线程老杜讲的最后一个很像?
老杜源码:
package com.bjpowernode.java.thread; import java.util.ArrayList; import java.util.List; /* 1、使用wait方法和notify方法实现“生产者和消费者模式” 2、什么是“生产者和消费者模式”? 生产线程负责生产,消费线程负责消费。 生产线程和消费线程要达到均衡。 这是一种特殊的业务需求,在这种特殊的情况下需要使用wait方法和notify方法。 3、wait和notify方法不是线程对象的方法,是普通java对象都有的方法。 4、wait方法和notify方法建立在线程同步的基础之上。因为多线程要同时操作一个仓库。有线程安全问题。 5、wait方法作用:o.wait()让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁。 6、notify方法作用:o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁。 7、模拟这样一个需求: 仓库我们采用List集合。 List集合中假设只能存储1个元素。 1个元素就表示仓库满了。 如果List集合中元素个数是0,就表示仓库空了。 保证List集合中永远都是最多存储1个元素。 必须做到这种效果:生产1个消费1个。 */ public class ThreadTest16 { public static void main(String[] args) { // 创建1个仓库对象,共享的。 List list = new ArrayList(); // 创建两个线程对象 // 生产者线程 Thread t1 = new Thread(new Producer(list)); // 消费者线程 Thread t2 = new Thread(new Consumer(list)); t1.setName("生产者线程"); t2.setName("消费者线程"); t1.start(); t2.start(); } } // 生产线程 class Producer implements Runnable { // 仓库 private List list; public Producer(List list) { this.list = list; } @Override public void run() { // 一直生产(使用死循环来模拟一直生产) while(true){ // 给仓库对象list加锁。 synchronized (list){ if(list.size() > 0){ // 大于0,说明仓库中已经有1个元素了。 try { // 当前线程进入等待状态,并且释放Producer之前占有的list集合的锁。 list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 程序能够执行到这里说明仓库是空的,可以生产 Object obj = new Object(); list.add(obj); System.out.println(Thread.currentThread().getName() + "--->" + obj); // 唤醒消费者进行消费 list.notifyAll(); } } } } // 消费线程 class Consumer implements Runnable { // 仓库 private List list; public Consumer(List list) { this.list = list; } @Override public void run() { // 一直消费 while(true){ synchronized (list) { if(list.size() == 0){ try { // 仓库已经空了。 // 消费者线程等待,释放掉list集合的锁 list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 程序能够执行到此处说明仓库中有数据,进行消费。 Object obj = list.remove(0); System.out.println(Thread.currentThread().getName() + "--->" + obj); // 唤醒生产者生产。 list.notifyAll(); } } } }
仔细看看,这两段代码表达的是同一段意思。前者步骤更清晰,封装的很好。前者运用了那个创建线程的步骤。。。。无语
-
虚假唤醒问题:(我称虚假唤醒为偷渡,是不是很形象)
(1)在多个生成者和消费者的情况下会出现虚假唤醒问题。
(2)wait()方法特点:在哪里睡,就在哪里醒。
(3)防止刚刚等待,然后被唤醒,然后又抢到锁的情况。
(4)倘若在这种情况用if包裹wait()方法,在哪里睡,在那醒,结果就可以逃脱判断筛选(偷渡!!!)
(5)其实在jdk的API文档中就说明了该问题,官方推荐使用whlie对wait()进行包裹
(6)三个问题:
1)然而,我还有个问题,难道在只有一个生产者和一个消费者的情况下,就不会出现虚假唤醒的可能吗?
当执行到wait()方法时,会让此线程等待,并释放锁。它一直在这行代码等待。所以不往下执行。所以并不会出行自己唤醒自己的可能,所以也就不可能出行虚假唤醒问题。
2)那为什么多个生产者和多个消费者就会出现呢虚假唤醒问题呢?
当锁连续被3个不同生产者或者3个消费者抢到,并且第4次被消费者或生产者抢到,以生产者为例:
A. 生产者1抢到锁,生产+1,代码执行结束,释放锁。
B. 生产者2抢到锁,进入if判断,等待并释放锁。
C. 生产者1抢到锁,进入if判断,等待并释放锁。
D. 消费者1抢到锁,生产-1,唤醒所有。此时库存为0
E. 生产者1抢到锁,生产+1,唤醒所有。
F. 生产者2抢到锁,生产+1,唤醒所有。
当然还有其他种情况。
3)为什么老杜的代码,会自己唤醒自己呢?
老杜讲错了,它不能唤醒自己,应为当连续抢到锁的时候,会进入wait()等待状态。不可能会往下执行,直接被另一个线程抢 走锁了。
本想着断点调试一下,没想到我这么菜,操
-
Lock实现案例:
(1)Lock接口下有一个实现类,后两个是接口:可重入锁,读锁,写锁。
(2)
Condition
实例实质上被绑定到一个锁上。要为特定Lock
实例获得Condition
实例,请使用其newCondition()
方法。(3)用notify()通知时,JVM会随机唤醒某个等待的线程, 使用Condition类可以进行选择性通知,Condition比较常用的两个方法:
● await()会使当前线程等待,同时会释放锁,当其他线程调用signal()时,线程会重新获得锁并继续执行。
● signal()用于唤醒一个等待的线程。
● signalAll()唤醒所有线程
(4)为什么Lock的形式解锁要设置为默认?
synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现 象发生;而 Lock 在发生异常时,如果没有主动 通过 unLock()去释放锁,则很 可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁;
(4)源码:
package com.atguigu.lock; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; //第一步 创建资源类,定义属性和操作方法 class Share { private int number = 0; //创建Lock private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); //+1 public void incr() throws InterruptedException { //上锁 lock.lock(); try { //判断 while (number != 0) { condition.await(); } //干活 number++; System.out.println(Thread.currentThread().getName()+" :: "+number); //通知 condition.signalAll(); }finally { //解锁 lock.unlock(); } } //-1 public void decr() throws InterruptedException { lock.lock(); try { while(number != 1) { condition.await(); } number--; System.out.println(Thread.currentThread().getName()+" :: "+number); condition.signalAll(); }finally { lock.unlock(); } } } public class ThreadDemo2 { public static void main(String[] args) { Share share = new Share(); new Thread(()->{ for (int i = 1; i <=10; i++) { try { share.incr(); } catch (InterruptedException e) { e.printStackTrace(); } } },"AA").start(); new Thread(()->{ for (int i = 1; i <=10; i++) { try { share.decr(); } catch (InterruptedException e) { e.printStackTrace(); } } },"BB").start(); new Thread(()->{ for (int i = 1; i <=10; i++) { try { share.incr(); } catch (InterruptedException e) { e.printStackTrace(); } } },"CC").start(); new Thread(()->{ for (int i = 1; i <=10; i++) { try { share.decr(); } catch (InterruptedException e) { e.printStackTrace(); } } },"DD").start(); } }
-
线程间定制化通信(源码)
package com.atguigu.lock; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; //第一步 创建资源类 class ShareResource { //定义标志位 private int flag = 1; // 1 AA 2 BB 3 CC //创建Lock锁 private Lock lock = new ReentrantLock(); //创建三个condition private Condition c1 = lock.newCondition(); private Condition c2 = lock.newCondition(); private Condition c3 = lock.newCondition(); //打印5次,参数第几轮 public void print5(int loop) throws InterruptedException { //上锁 lock.lock(); try { //判断 while(flag != 1) { //等待 c1.await(); } //干活 for (int i = 1; i <=5; i++) { System.out.println(Thread.currentThread().getName()+" :: "+i+" :轮数:"+loop); } //通知 flag = 2; //修改标志位 2 c2.signal(); //通知BB线程 }finally { //释放锁 lock.unlock(); } } //打印10次,参数第几轮 public void print10(int loop) throws InterruptedException { lock.lock(); try { while(flag != 2) { c2.await(); } for (int i = 1; i <=10; i++) { System.out.println(Thread.currentThread().getName()+" :: "+i+" :轮数:"+loop); } //修改标志位 flag = 3; //通知CC线程 c3.signal(); }finally { lock.unlock(); } } //打印15次,参数第几轮 public void print15(int loop) throws InterruptedException { lock.lock(); try { while(flag != 3) { c3.await(); } for (int i = 1; i <=15; i++) { System.out.println(Thread.currentThread().getName()+" :: "+i+" :轮数:"+loop); } //修改标志位 flag = 1; //通知AA线程 c1.signal(); }finally { lock.unlock(); } } } public class ThreadDemo3 { public static void main(String[] args) { ShareResource shareResource = new ShareResource(); new Thread(()->{ for (int i = 1; i <=10; i++) { try { shareResource.print5(i); } catch (InterruptedException e) { e.printStackTrace(); } } },"AA").start(); new Thread(()->{ for (int i = 1; i <=10; i++) { try { shareResource.print10(i); } catch (InterruptedException e) { e.printStackTrace(); } } },"BB").start(); new Thread(()->{ for (int i = 1; i <=10; i++) { try { shareResource.print15(i); } catch (InterruptedException e) { e.printStackTrace(); } } },"CC").start(); } }
四. 集合的线程安全
1. ArrayList集合线程不安全演示
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* list集合线程不安全,
可直接看源码,查看调用的方法那是否设置了锁(Synchronized,或Lock锁)
*/
public class ThreadDemo4 {
public static void main(String[] args) {
//创建ArrayList集合
List<String> list = new ArrayList<>();
for (int i = 0; i <30; i++) {
//多个线程共同修改数据,报出异常
new Thread(()->{
//向集合添加内容
list.add(UUID.randomUUID().toString().substring(0,8));
//从集合获取内容
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
(1)解决方案-Vector
(jdk1.0方案,太老了)
在接口List下除了ArrayList实现类外,还有一个Vector实现类。
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* list集合线程不安全
*/
public class ThreadDemo4 {
public static void main(String[] args) {
//创建ArrayList集合
// List<String> list = new ArrayList<>();
// Vector解决
List<String> list = new Vector<>();
// CopyOnWriteArrayList解决
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i <30; i++) {
new Thread(()->{
//向集合添加内容
list.add(UUID.randomUUID().toString().substring(0,8));
//从集合获取内容
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
(2)解决方案-Collections
(这种方式也很老,这两种方案用的都不多,用的最多的是JUC里的解决方案)
借助工具类Collections下的静方法synchronizedList(List list)
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* list集合线程不安全
*/
public class ThreadDemo4 {
public static void main(String[] args) {
//创建ArrayList集合
List<String> list = new ArrayList<>();
// Vector解决
// List<String> list = new Vector<>();
//Collections解决
List<String> list = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i <30; i++) {
new Thread(()->{
//向集合添加内容
list.add(UUID.randomUUID().toString().substring(0,8));
//从集合获取内容
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
(3)解决方案-CopyOnWriteArrayList
这个类也被称为“写时复制技术”
package com.atguigu.lock;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* list集合线程不安全
*/
public class ThreadDemo4 {
public static void main(String[] args) {
//创建ArrayList集合
// List<String> list = new ArrayList<>();
// 1.Vector解决
// List<String> list = new Vector<>();
//2.Collections解决
// List<String> list = Collections.synchronizedList(new ArrayList<>());
//3.CopyOnWriteArrayList解决
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i <30; i++) {
new Thread(()->{
//向集合添加内容
list.add(UUID.randomUUID().toString().substring(0,8));
//从集合获取内容
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
源码分析:
public boolean add(E e) {
//可重入锁
final ReentrantLock lock = this.lock;
//上锁
lock.lock();
try {
//1.得到内容
Object[] elements = getArray();
//2.得到旧数组长度
int len = elements.length;
//拷贝到一个新数组,以扩展一个元素的形式拷贝到新数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
//将内容写到新数组,len是旧数组的长度,他他作为下标是应该是扩展一位的意思。
newElements[len] = e;
//新数组覆盖之前的旧数组
setArray(newElements);
return true;
} finally {
//解锁
lock.unlock();
}
}
//妙啊!!!
2. HashSet线程不安全
类似上面操作就行,解决方案 CopyOnWriteArraySet
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
public class ThreadDemo4 {
public static void main(String[] args) {
//演示Hashset
Set<String> set = new HashSet<>();
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 0; i <30; i++) {
new Thread(()->{
//向集合添加内容
set.add(UUID.randomUUID().toString().substring(0,8));
//从集合获取内容
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
3. HashMap线程不安全
解决方案 ConcurrentHashMap
//演示HashMap
// Map<String,String> map = new HashMap<>();
Map<String,String> map = new ConcurrentHashMap<>();
for (int i = 0; i <30; i++) {
String key = String.valueOf(i);
new Thread(()->{
//向集合添加内容
map.put(key,UUID.randomUUID().toString().substring(0,8));
//从集合获取内容
System.out.println(map);
},String.valueOf(i)).start();
五. 多线程锁
1. 演示锁的八种情况
演示代码:
package com.atguigu.sync;
import java.util.concurrent.TimeUnit;
class Phone {
public static synchronized void sendSMS() throws Exception {
//停留4秒
TimeUnit.SECONDS.sleep(4);
System.out.println("------sendSMS");
}
public synchronized void sendEmail() throws Exception {
System.out.println("------sendEmail");
}
public void getHello() {
System.out.println("------getHello");
}
}
/**
* @Description: 8锁
*
1 标准访问,先打印短信还是邮件
------sendSMS
------sendEmail
2 停4秒在短信方法内,先打印短信还是邮件
------sendSMS
------sendEmail
3 新增普通的hello方法,是先打短信还是hello
------getHello
------sendSMS
4 现在有两部手机,先打印短信还是邮件
------sendEmail
------sendSMS
5 两个静态同步方法,1部手机,先打印短信还是邮件
------sendSMS
------sendEmail
6 两个静态同步方法,2部手机,先打印短信还是邮件
------sendSMS
------sendEmail
7 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
------sendEmail
------sendSMS
8 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
------sendEmail
------sendSMS
*/
public class Lock_8 {
public static void main(String[] args) throws Exception {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "AA").start();
Thread.sleep(100);
new Thread(() -> {
try {
// phone.sendEmail();
// phone.getHello();
phone2.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
}, "BB").start();
}
}
8锁只不过是这几种情况而已,并不是官方定义的。
总结三个知识点:
(1)对于普通同步方法,锁是当前实例对象。
(2)对于静态同步方法,锁是当前类的 Class 对象。
(3)对于同步方法块,锁是 Synchonized 括号里配置的对象
2. 公平锁和非公平锁
看源码秒懂!
(1)非公平锁:private Lock lock = new ReentrantLock();当可重入锁的构造函数无参,或者参数为false时,为非公平锁
(2)公平锁:相对的为true时,为公平锁。
(3)源码:
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
(4)举例理解:
1)a,b,c3个人卖30张票,非公平锁时就是,一个人把票卖完了;公平锁就是3个人都能卖到票。都有钱赚。
2)公交车找座位:非公平锁直接坐那。公平锁就是还要礼貌的问候:”这里有人吗?“。。。
(5)非公平锁和公平锁的优缺点
1)非公平锁:
优点:效率高
缺点:容易造成线程饿死
1)公平锁:
优点:阳光普照,都能吃上饭
缺点:效率相对低
3. 可重入锁
(1)可重入锁也称为递归锁。
(2)synchronized 和 Lock都是可重入锁。前者隐式,后者显式。
(3)那到底什么是可重入锁?说实话这两节可我没太听懂,但是这篇文章讲清楚了。
https://blog.csdn.net/w8y56f/article/details/89554060
(4)什么是可重入锁:什么是 “可重入”,可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁
(5)synchronized源码案例:
package com.atguigu.sync;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
//可重入锁
public class SyncLockDemo {
public synchronized void add() {
add();
}
public static void main(String[] args) {
// new SyncLockDemo().add();
// synchronized
Object o = new Object();
new Thread(()->{
synchronized(o) {
System.out.println(Thread.currentThread().getName()+" 外层");
synchronized (o) {
System.out.println(Thread.currentThread().getName()+" 中层");
synchronized (o) {
System.out.println(Thread.currentThread().getName()+" 内层");
}
}
}
},"t1").start();
}
}
(6)Lock源码案例:
这个要注意,ReentrantLock 和 synchronized 不一样,需要手动释放锁,所以使用 ReentrantLock的时候一定要手动释放锁,并且加锁次数和释放次数要一样,容易报死锁或者栈溢出异常。
public class SyncLockDemo {
public synchronized void add() {
add();
}
public static void main(String[] args) {
//Lock演示可重入锁
Lock lock = new ReentrantLock();
//创建线程
new Thread(()->{
try {
//上锁
lock.lock();
System.out.println(Thread.currentThread().getName()+" 外层");
try {
//上锁
lock.lock();
System.out.println(Thread.currentThread().getName()+" 内层");
}finally {
//释放锁
lock.unlock();
}
}finally {
//释放做
lock.unlock();
}
},"t1").start();
//创建新线程
new Thread(()->{
lock.lock();
System.out.println("aaaa");
lock.unlock();
},"aa").start();
}
}
4. 死锁
其实这里和老杜讲的多线程那,相差不大。
上图的文字内容很重要!!!
演示源码:
import java.util.concurrent.TimeUnit;
/**
* 演示死锁
*/
public class DeadLock {
//创建两个对象
static Object a = new Object();
static Object b = new Object();
public static void main(String[] args) {
new Thread(()->{
synchronized (a) {
System.out.println(Thread.currentThread().getName()+" 持有锁a,试图获取锁b");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b) {
System.out.println(Thread.currentThread().getName()+" 获取锁b");
}
}
},"A").start();
new Thread(()->{
synchronized (b) {
System.out.println(Thread.currentThread().getName()+" 持有锁b,试图获取锁a");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a) {
System.out.println(Thread.currentThread().getName()+" 获取锁a");
}
}
},"B").start();
}
}
(1)如何验证是否是死锁?
1)通过JDK中bin目录的jps.exe运行文件,获取进程端口号(这个命令类似LInux中的ps -ef命令,查看正在运行的进程)
命令:jsp -l
2)然后使用jvm自带的堆栈跟踪工具——jstack
命令:jstack 进程端口号
(2)什么是死锁?
答案在图中
(2)死锁产生的原因?
答案在图中
六. Callable接口
1. Callable概述
2. Callable使用方式
(1)为了实现 Runnable,需要实现不返回任何内容的 run()方法,而对于 Callable,需要实现在完成时返回结果的 call()方法。
(2)call()方法可以引发异常,而 run()则不能。
(3)为实现 Callable 而必须重写 call 方法。
(4)不能直接替换 runnable,因为 Thread 类的构造方法根本没有 Callable。
3. FutureTask未来任务类
(1)由上述第4条可以得出,下图:
(2)项目演示源码:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//比较两个接口
//实现Runnable接口
class MyThread1 implements Runnable {
@Override
public void run() {
}
}
//实现Callable接口
class MyThread2 implements Callable {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+" come in callable");
return 200;
}
}
public class Demo1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//Runnable接口创建线程
new Thread(new MyThread1(),"AA").start();
//Callable接口,报错
// new Thread(new MyThread2(),"BB").start();
//FutureTask
FutureTask<Integer> futureTask1 = new FutureTask<>(new MyThread2());
//lam表达式
FutureTask<Integer> futureTask2 = new FutureTask<>(()->{
System.out.println(Thread.currentThread().getName()+" come in callable");
return 1024;
});
//创建一个线程
new Thread(futureTask2,"lucy").start();
new Thread(futureTask1,"mary").start();
// while(!futureTask2.isDone()) {
// System.out.println("wait.....");
// }
//调用FutureTask的get方法
System.out.println(futureTask2.get());
System.out.println(futureTask1.get());
System.out.println(Thread.currentThread().getName()+" come over");
//FutureTask原理 未来任务
/**
* 1、老师上课,口渴了,去买票不合适,讲课线程继续。
* 单开启线程找班上班长帮我买水,把水买回来,需要时候直接get
*
* 2、4个同学, 1同学 1+2...5 , 2同学 10+11+12....50, 3同学 60+61+62, 4同学 100+200
* 第2个同学计算量比较大,
* FutureTask单开启线程给2同学计算,先汇总 1 3 4 ,最后等2同学计算位完成,统一汇总
*
* 3、考试,做会做的题目,最后看不会做的题目
*
* 汇总一次
*
*/
}
}
(3)FutureTask概述与原理,为什么叫未来任务来类?
举例:4个同学,1同学 1+2…5, 2同学 10+11+12…50, 3同学 60+61+62, 4同学 100+200
第2个同学计算量比较大,
FutureTask单开启线程给2同学计算,先汇总 1 3 4 ,最后等2同学计算位完成,统一汇总
其实这里我仍然有疑惑,先放着吧!!!
=拓展========
4. 注解@FunctionalInterface 及 Lambda表达式
(1)函数式接口在java中是指:有且仅有一个抽象方法的接口,它是java1.8的新特性,函数式接口上添加注解@FunctionalInterface。
(2)Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。Lambda 允许把函数作为一个方法的参数(函数作为参数 传递进方法中)。使用 Lambda 表达式可以使代码变的更加简洁紧凑。
(3)函数式接口,即适用于函数式编程场景的接口。而java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda 使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
(4)个人理解:是满足@FunctionalInterface接口条件的可以用Lambda表达式吗?
(5)参考文档:https://blog.csdn.net/qq_32224047/article/details/106603405
https://blog.csdn.net/linysuccess/article/details/104751843
七. JUC强大的辅助类
1. 减少计数CountDownLatch
CountDownLatch 类可以设置一个计数器,然后通过 countDown 方法来进行 减 1 的操作,使用 await 方法等待计数器不大于 0,然后继续执行 await 方法 之后的语句。
(1)CountDownLatch 主要有两个方法,当一个或多个线程调用 await 方法时,这 些线程会阻塞
(2)其它线程调用 countDown 方法会将计数器减 1(调用 countDown 方法的线程 不会阻塞)
(3)当计数器的值变为 0 时,因 await 方法阻塞的线程会被唤醒,继续执行
场景: 6 个同学陆续离开教室后值班同学才可以关门。
CountDownLatchDemo:
import java.util.concurrent.CountDownLatch;
//演示 CountDownLatch
public class CountDownLatchDemo {
//6个同学陆续离开教室之后,班长锁门
public static void main(String[] args) throws InterruptedException {
//创建CountDownLatch对象,设置初始值
CountDownLatch countDownLatch = new CountDownLatch(6);
//6个同学陆续离开教室之后
for (int i = 1; i <=6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" 号同学离开了教室");
//计数 -1
countDownLatch.countDown();
},String.valueOf(i)).start();
}
//等待
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+" 班长锁门走人了");
}
}
2. 循环栅栏CyclicBarrier
CyclicBarrier 看英文单词可以看出大概就是循环阻塞的意思,在使用中 CyclicBarrier 的构造方法第一个参数是目标障碍数,每次执行 CyclicBarrier 一 次障碍数会加一,如果达到了目标障碍数,才会执行 cyclicBarrier.await()之后 的语句。可以将 CyclicBarrier 理解为加 1 操作
场景: 集齐 7 颗龙珠就可以召唤神龙
CyclicBarrierDemo:
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
//集齐7颗龙珠就可以召唤神龙
public class CyclicBarrierDemo {
//创建固定值
private static final int NUMBER = 7;
public static void main(String[] args) {
//创建CyclicBarrier
CyclicBarrier cyclicBarrier =
new CyclicBarrier(NUMBER,()->{
System.out.println("*****集齐7颗龙珠就可以召唤神龙");
});
//集齐七颗龙珠过程
for (int i = 1; i <=7; i++) {
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+" 星龙被收集到了");
//等待
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
3. 信号灯Semaphore
Semaphore 的构造方法中传入的第一个参数是最大信号量(可以看成最大线 程池),每个信号量初始化为一个最多只能分发一个许可证。使用 acquire 方 法获得许可证,release 方法释放许可。
场景: 抢车位, 6 部汽车 3 个停车位
SemaphoreDemo:
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
//6辆汽车,停3个车位
public class SemaphoreDemo {
public static void main(String[] args) {
//创建Semaphore,设置许可数量
Semaphore semaphore = new Semaphore(3);
//模拟6辆汽车
for (int i = 1; i <=6; i++) {
new Thread(()->{
try {
//抢占
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+" 抢到了车位");
//设置随机停车时间
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
System.out.println(Thread.currentThread().getName()+" ------离开了车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
八. ReentrantReadWriteLock读写锁
1. 乐观锁和悲观锁
2. 读写锁及表锁和行锁
(1)表锁:把整张表锁住;
行锁:把一行锁住。
所以表锁有发生死锁的可能。
(2)什么是读写锁?
JAVA 的并发包提供了读写锁 ReentrantReadWriteLock, 它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称 为排他锁
(2)读写锁:
1)前者共享锁,可能发生死锁;后者独占锁,也可能发生死锁。
2)读可以一起读,写只能一个人写。即别人读的时候不能写,别人写的时候也不能写。这是发生死锁的根本。
3)无论是读还是写,都有可能发生死锁。
(3)读锁:读的时候可以进行写的操作,1在修改时,要等2的读完成后;而2的修改,要等1读完后,可能会发生死锁。
写锁:线程1和2在同时操作两个两个记录,线程1要等2操作完第二条记录,线程2要等1操作完第一条记录。互相等待,产生死锁。
3. 读写锁案例演示
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
//资源类
class MyCache {
//创建map集合
private volatile Map<String,Object> map = new HashMap<>();
//创建读写锁对象
private ReadWriteLock rwLock = new ReentrantReadWriteLock();
//放数据
public void put(String key,Object value) {
//添加写锁
rwLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+" 正在写操作"+key);
//暂停一会
TimeUnit.MICROSECONDS.sleep(300);
//放数据
map.put(key,value);
System.out.println(Thread.currentThread().getName()+" 写完了"+key);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放写锁
rwLock.writeLock().unlock();
}
}
//取数据
public Object get(String key) {
//添加读锁
rwLock.readLock().lock();
Object result = null;
try {
System.out.println(Thread.currentThread().getName()+" 正在读取操作"+key);
//暂停一会
TimeUnit.MICROSECONDS.sleep(300);
result = map.get(key);
System.out.println(Thread.currentThread().getName()+" 取完了"+key);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放读锁
rwLock.readLock().unlock();
}
return result;
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) throws InterruptedException {
MyCache myCache = new MyCache();
//创建线程放数据
for (int i = 1; i <=5; i++) {
final int num = i;
new Thread(()->{
myCache.put(num+"",num+"");
},String.valueOf(i)).start();
}
TimeUnit.MICROSECONDS.sleep(300);
//创建线程取数据
for (int i = 1; i <=5; i++) {
final int num = i;
new Thread(()->{
myCache.get(num+"");
},String.valueOf(i)).start();
}
}
}
=拓展==
(1)volatile关键字的作用
https://blog.csdn.net/weixin_30342639/article/details/91356608
4. 读写锁的演变
5. 读写锁的降级
读写锁的降级只针对写锁降级为读锁。
(1)如何将写锁降为读锁?
上图中的前三步。即锁降级指的是写锁降级成为读锁。锁降级是指把持住当前拥有的写锁的同时,再获取到读锁,随后释放写锁的过程。
(2)项目演示:
import java.util.concurrent.locks.ReentrantReadWriteLock;
//演示读写锁降级
public class Demo1 {
public static void main(String[] args) {
//可重入读写锁对象
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();//读锁
ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();//写锁
//锁降级
//2 获取读锁
readLock.lock();
System.out.println("---read");
//1 获取写锁
writeLock.lock();
System.out.println("atguigu");
//3 释放写锁
//writeLock.unlock();
//4 释放读锁
//readLock.unlock();
}
}
九. BlockingQueue阻塞队列
1. 阻塞队列概述,什么是阻塞队列?
这个名词包含两种含义,一种是队列
,一种是阻塞
。
- 队列:
如上所示,队列就是一种排队样式。
队列如同一个管道,由数据产生者生产信息,通过管道传递至数据消费者。
遵循FIFO
规则。即:先进先出
。 - 阻塞:
阻塞的含义可以分为一直等待
、期限等待
。
2. 阻塞队列的架构
3. 阻塞队列分类
(1) ArrayBlockingQueue(常用):一句话总结: 由数组结构组成的有界阻塞队列。
(2) LinkedBlockingQueue(常用):一句话总结: 由链表结构组成的有界(但大小默认值为 integer.MAX_VALUE)阻塞队列。
ArrayBlockingQueue 和 LinkedBlockingQueue 是两个最普通也是最常用 的阻塞队列,一般情况下,在处理多线程间的生产者消费者问题,使用这两个 类足以。
后5个请查看课件=======
(3)DelayQueue
(4)PriorityBlockingQueue
(5)SynchronousQueue
(6)SynchronousQueue
(4)LinkedBlockingDeque
4. 阻塞队列核心方法
//函数演示:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
//阻塞队列
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
//创建阻塞队列
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
//第一组
// System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
//System.out.println(blockingQueue.element());
//System.out.println(blockingQueue.add("w"));
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
//第二组
// System.out.println(blockingQueue.offer("a"));
// System.out.println(blockingQueue.offer("b"));
// System.out.println(blockingQueue.offer("c"));
// System.out.println(blockingQueue.offer("www"));
//
// System.out.println(blockingQueue.poll());
// System.out.println(blockingQueue.poll());
// System.out.println(blockingQueue.poll());
// System.out.println(blockingQueue.poll());
//第三组
// blockingQueue.put("a");
// blockingQueue.put("b");
// blockingQueue.put("c");
// //blockingQueue.put("w");
//
// System.out.println(blockingQueue.take());
// System.out.println(blockingQueue.take());
// System.out.println(blockingQueue.take());
// System.out.println(blockingQueue.take());
//第四组
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("w",3L, TimeUnit.SECONDS));
}
}
十. ThreadPool线程池
1. 线程池概述
(1)线程池嘛,顾名思义。王者荣耀有英雄池,线程也有线程池。是不是类似数据库连接池?
(2)线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销, 进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理 者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代 价。线程池不仅能够保证内核的充分利用,还能防止过分调度。
(3)线程池的优势: 线程池做的工作只要是控制运行的线程数量,处理过程中将任 务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量, 超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
(4)它的主要特点为:
• 降低资源消耗: 通过重复利用已创建的线程降低线程创建和销毁造成的销耗。
• 提高响应速度: 当任务到达时,任务可以不需要等待线程创建就能立即执行。
• 提高线程的可管理性: 线程是稀缺资源,如果无限制的创建,不仅会销耗系统资 源,还会降低系统的稳定性,使用线程池可以进 行统一的分配,调优和监控。
• Java 中的线程池是通过 Executor 框架实现的,该框架中用到了 Executor,Executors, ExecutorService, ThreadPoolExecutor 这几个类
(5)下面这篇文章能让我们更好的了解线程池:https://blog.csdn.net/heihaozi/article/details/102882698
2. 线程池架构
3. 线程池使用方式(常用的)
(1)Executors.newFixedThreadPool(int),一池N线程
项目演示:银行为10个顾客办理业务,根据线程池使用方式不同,所开的窗口(线程)不同。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//演示线程池三种常用分类
public class ThreadPoolDemo1 {
public static void main(String[] args) {
//一池五线程
ExecutorService threadPool1 = Executors.newFixedThreadPool(5); //5个窗口
//10个顾客请求
try {
for (int i = 1; i <=10; i++) {
//执行
threadPool1.execute(()->{
System.out.println(Thread.currentThread().getName()+" 办理业务");
});
}
}catch (Exception e) {
e.printStackTrace();
}finally {
//关闭。线程池中的线程,用完后还要放回池中,供别的请求使用
//将它放入finally中让结构更明确
threadPool1.shutdown();
}
}
}
(2)Executors.newSingleThreadExecutor(),一个任务一个任务执行,一池一线程
项目演示:
//第二种方式
public class ThreadPoolDemo1 {
public static void main(String[] args) {
//一池一线程
ExecutorService threadPool2 = Executors.newSingleThreadExecutor(); //一个窗口
//10个顾客请求
try {
for (int i = 1; i <=10; i++) {
//执行
threadPool2.execute(()->{
System.out.println(Thread.currentThread().getName()+" 办理业务");
});
}
}catch (Exception e) {
e.printStackTrace();
}finally {
//关闭
threadPool2.shutdown();
}
}
}
(3)Executors.newCachedThreadPool(),线程池根据需求创建线程,可扩容,遇强则强
而在不需要那么多线程的时候,又会自行关闭
项目演示:
//第三种方式
public class ThreadPoolDemo1 {
public static void main(String[] args) {
//一池可扩容线程
ExecutorService threadPool3 = Executors.newCachedThreadPool();
//10个顾客请求
try {
for (int i = 1; i <=10; i++) {
//执行
threadPool3.execute(()->{
System.out.println(Thread.currentThread().getName()+" 办理业务");
});
}
}catch (Exception e) {
e.printStackTrace();
}finally {
//关闭
threadPool3.shutdown();
}
}
}
4. 线程池底层原理
出现了三种方式是因为创建线程池的时候,调用了Executors不同的方法。但是这三个方法的底层都是调用了ThreadPoolExecutor()方法,只是他们使用的参数不同。
所以,出现不同方式的原因是因为参数不同。下面说明ThreadPoolExecutor()方法的7个参数。
5. 线程池的七个参数
(1)
(2)这篇文章很好:https://blog.csdn.net/ye17186/article/details/89467919
(3)七个参数:
1)corePoolSize 线程池的核心线程数
2)maximumPoolSize 能容纳的最大线程数
3)keepAliveTime 空闲线程存活时间
4)unit 存活的时间单位
5)workQueue 存放提交但未执行任务的队列
6)threadFactory 创建线程的工厂类
7)handler 等待队列满后的拒绝策略
6. 线程池底层工作流程
(1)底层工作流程
这个要求会描述!!!艹
在课件中有,注意以后忘了再看!!!。。。
可以在第39集听老师讲解。
(2)四种拒绝策略
7. 自定义线程池
在真实的开发中,我们都是使用自定义的线程池。为什么呢?
import java.util.concurrent.*;
//自定义线程池创建
public class ThreadPoolDemo2 {
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
2L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
//10个顾客请求
try {
for (int i = 1; i <=10; i++) {
//执行
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" 办理业务");
});
}
}catch (Exception e) {
e.printStackTrace();
}finally {
//关闭
threadPool.shutdown();
}
}
}
十一. Fork/Join分支合并框架
具体的请看课件吧!!!
Fork/Join 它可以将一个大的任务拆分成多个子任务进行并行处理,最后将子 任务结果合并成最后的计算结果,并进行输出。Fork/Join 框架要完成两件事 情:
Fork:把一个复杂任务进行分拆,大事化小
Join:把分拆任务的结果进行合并
场景: 生成一个计算任务,计算 1+2+3…+100,
每 10 个数切分一个 子任务
import java.util.concurrent.*;
class MyTask extends RecursiveTask<Integer> {
//拆分差值不能超过10,计算10以内运算
private static final Integer VALUE = 10;
private int begin ;//拆分开始值
private int end;//拆分结束值
private int result ; //返回结果
//创建有参数构造
public MyTask(int begin,int end) {
this.begin = begin;
this.end = end;
}
//拆分和合并过程
@Override
protected Integer compute() {
//判断相加两个数值是否大于10
if((end-begin)<=VALUE) {
//相加操作
for (int i = begin; i <=end; i++) {
result = result+i;
}
} else {//进一步拆分
//获取中间值
int middle = (begin+end)/2;
//拆分左边
MyTask task01 = new MyTask(begin,middle);
//拆分右边
MyTask task02 = new MyTask(middle+1,end);
//调用方法拆分
task01.fork();
task02.fork();
//合并结果
result = task01.join()+task02.join();
}
return result;
}
}
public class ForkJoinDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建MyTask对象
MyTask myTask = new MyTask(0,100);
//创建分支合并池对象
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Integer> forkJoinTask = forkJoinPool.submit(myTask);
//获取最终合并之后结果
Integer result = forkJoinTask.get();
System.out.println(result);
//关闭池对象
forkJoinPool.shutdown();
}
}
十二. CompletableFuture异步回调
没有返回值的异步调用:runAsync()
有返回值的异步调用:supplyAsync()
项目演示:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
//异步调用和同步调用
public class CompletableFutureDemo {
public static void main(String[] args) throws Exception {
//同步调用
CompletableFuture<Void> completableFuture1 = CompletableFuture.runAsync(()->{
System.out.println(Thread.currentThread().getName()+" : CompletableFuture1");
});
completableFuture1.get();
//mq消息队列
//异步调用
CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+" : CompletableFuture2");
//模拟异常
int i = 10/0;
return 1024;
});
completableFuture2.whenComplete((t,u)->{
System.out.println("------t="+t); //返回值
System.out.println("------u="+u); .//异常信息
}).get();
}
}
https://blog.csdn.net/qq_31865983/article/details/106137777
好难啊这个,这里老师没有讲多。。
后两章没有理解好