目录
4.2.3通过JUC中CopyOnWriteArrayList
12.1.2Future 与 CompletableFuture
一、概述
1.1什么是JUC
1.2进程与线程
1.2.1线程的状态
1.2.2wait和sleep
- sleep 是 Thread 的静态方法,wait 是 Object 的方法,任何对象实例都能调用。
- sleep 不会释放锁,它也不需要占用锁。wait 会释放锁,但调用它的前提是当前线程占有锁(即代码要在 synchronized 中)。
- 它们都可以被 interrupted 方法中断。
1.2.3并发和并行
1.2.4管程
1.2.5用户线程和守护线程
用户线程 : 平时用到的普通线程 , 自定义线程守护线程 : 运行在后台 , 是一种特殊的线程 , 比如垃圾回收当主线程结束后 , 用户线程还在运行 ,JVM 存活如果没有用户线程 , 都是守护线程 ,JVM 结束
二、Lock接口
2.1 Synchronized
- 修饰一个代码块
被修饰的代码块称为同步语句块,其作用的范围是大括号{} 括起来的代码,作用的对象是调用这个代码块的对象; -
修饰一个方法,
被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;虽然可以使用 synchronized 来定义方法,但 synchronized 并不属于方法定义的一部分,因此,synchronized 关键字不能被继承。如果在父类中的某个方法使用了 synchronized 关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized 关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。 -
修改一个静态的方法
其作用的范围是整个静态方法,作用的对象是这个类的所有对象; - 修改一个类
其作用的范围是 synchronized 后面括号括起来的部分,作用主的对象是这个类的所有对象。
2.2Lock接口,实现手动加锁和释放锁
与synchronized比较
- Lock 不是 Java 语言内置的,synchronized 是 Java 语言的关键字,因此是内置特性。Lock 是一个类,通过这个类可以实现同步访问;
-
Lock 和 synchronized 有一点非常大的不同,采用 synchronized 不需要用户去手动释放锁,当 synchronized 方法或者 synchronized 代码块执行完之后,系统会自动让线程释放对锁的占用;而 Lock 则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
2.2.1卖票的例子
package com.ms.juc.demo1.lock;
import java.util.concurrent.locks.ReentrantLock;
//第一步:创建资源类,创建属性和方法
class Ticket {
private int number = 30;
//创建可重入锁
private final ReentrantLock lock = new ReentrantLock();
public void sale() {//会自动上锁
//上锁
lock.lock();
//是否有余票
try {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出票,剩下:" + (--number));
}
} finally {//有无异常都要释放锁
//解锁
lock.unlock();
}
}
}
public class SaleTicket {
public static void main(String[] args) {
//第二步:创建爱你多个线程,调用资源类的操作方法
Ticket ticket = new Ticket();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "thread1").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "thread2").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "thread3").start();
}
}
2.3接口中的方法
三、线程间的通信
3.1synchronized实现
package com.ms.juc.demo1.线程间通信;
class Share{
private int number=0;
public synchronized void incr() throws InterruptedException {
if (number!=0){
this.wait();
}else{
number++;
System.out.println(Thread.currentThread().getName()+"+1操作");
//通知其他线程
this.notifyAll();
}
}
public synchronized void decr() throws InterruptedException {
if (number!=1){
this.wait();
}else {
number--;
System.out.println(Thread.currentThread().getName()+"-1操作");
//通知其他线程
this.notifyAll();
}
}
}
public class SyncDemo {
public static void main(String[] args) {
Share share = new Share();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"Thread1").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"Thread2").start();
}
}
效果:
存在的问题:
wait方法在哪里wait,就会在哪里醒,如果是if判断,等到被唤醒的时候就会跳过判断,所以要写到while循环中
3.2Lock实现
package com.ms.juc.demo1._1线程间通信;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Share0 {
private int number = 0;
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
public void incr() throws InterruptedException {
//加锁
lock.lock();
try {
while (number != 0) {
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName() + "+1操作,当前值为:" + number);
condition.signalAll();
} finally {
lock.unlock();
}
}
public void decr() throws InterruptedException {
//加锁
lock.lock();
try {
while (number != 1) {
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + "-1操作,当前值为:" + number);
condition.signalAll();
} finally {
lock.unlock();
}
}
}
public class LockDemo {
public static void main(String[] args) {
Share0 share = new Share0();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Thread1").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Thread2").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Thread3").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Thread4").start();
}
}
结果:
3.3线程间定制化通信
约定线程的执行顺序
案例
需求:
AA打印5次,BB打印10次,CC打印15次
重复10轮
为每个线程添加一个标志位
package com.ms.juc.demo1._1线程间通信.定制化通信;
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 3CC
private Lock lock = new ReentrantLock();
//创建3个condition
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
//各自标志位对应的方法
public void print5(int loop) throws InterruptedException {
//上锁
lock.lock();
try {
while (flag != 1) {
c1.await();
}
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "::" + i + "轮数:" + loop);
}
//通知下一个线程
flag = 2;
c2.signal();//通知BB线程
} finally {
lock.unlock();
}
}
public void print10(int loop) throws InterruptedException {
//上锁
lock.lock();
try {
while (flag != 2) {
c2.await();
}
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "::" + i + "轮数:" + loop);
}
//通知下一个线程
flag = 3;
c3.signal();//通知CC线程
} finally {
lock.unlock();
}
}
public void print15(int loop) throws InterruptedException {
//上锁
lock.lock();
try {
while (flag != 3) {
c3.await();
}
for (int i = 0; i < 15; i++) {
System.out.println(Thread.currentThread().getName() + "::" + i + "轮数:" + loop);
}
//通知下一个线程
flag = 1;
c1.signal();//通知AA线程
} finally {
lock.unlock();
}
}
}
public class PersonalDemo {
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();
}
}
四、集合的线程安全
4.1集合操作 Demo
arrayList的add方法时线程不安全的
package com.ms.juc.demo1._4集合的线程安全.demo;
import java.util.ArrayList;
import java.util.UUID;
public class ThreadDemo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
//多线程操作这个list
for (int i = 0; i < 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},"线程"+i).start();
}
}
}
可能会报异常
4.2解决
4.2.1vector
List<String> list = new Vector<>();
查看一下vector的add方法
4.2.2通过Collections工具类
List<String> list = Collections.synchronizedList(new ArrayList<>());
4.2.3通过JUC中CopyOnWriteArrayList
List<String> list = new CopyOnWriteArrayList<>();
该类使用写时复制技术
多个线程对集合可以并发读,但是每个线程对集合写的时候,都会拷贝一个新的集合,在新的集合上修改,最终合并
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();//上锁
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);//复制
newElements[len] = e;//在新的中修改
setArray(newElements);//覆盖之前
return true;
} finally {
lock.unlock();//解锁
}
}
4.3HashSet和HashMap线程不安全
4.3.1Set
Set<String> set=new HashSet<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(set);
},"线程"+i).start();
}
可能报异常
解决:
Set<String> set=new CopyOnWriteArraySet<>();
4.3.2Map
Map<String,String> map=new HashMap<>();
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(()->{
map.put(String.valueOf(finalI), UUID.randomUUID().toString().substring(0,8));
System.out.println(map);
},"线程"+i).start();
}
同样会报上述异常
解决:
Map<String,String> map=new ConcurrentHashMap<>();
五、多线程锁
结论 :一个对象里面如果有多个 synchronized 方法,某一个时刻内,只要一个线程去调用其中的一个 synchronized 方法了,其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized 方法,锁的是当前对象 this ,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized 方法加个普通方法后发现和同步锁无关换成两个对象后,不是同一把锁了,情况立刻变化。synchronized 实现同步的基础: Java 中的每一个对象都可以作为锁。具体表现为以下 3 种形式。
- 对于普通同步方法,锁是当前实例对象。
- 对于静态同步方法,锁是当前类的 Class 对象。
- 对于同步方法块,锁是 Synchonized 括号里配置的对象
当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,所以毋须等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁。所有的静态同步方法用的也是同一把锁——类对象本身,这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的。但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象!
5.1公平锁与非公平锁
5.1.1非公平锁
多个线程抢占同一个资源,可能资源最终都被一个或几个线程获取,其他线程出现饿死的情况
private final ReentrantLock lock = new ReentrantLock();
默认就是非公平锁
5.1.2公平锁
private final ReentrantLock lock = new ReentrantLock(true);
每个线程基本可以等分的获取资源
优缺点:
非公平锁:可能造成线程饿死,但是效率该
公平锁:阳光普照,效率较低
5.2可重入锁
synchronized(隐式的,自动上锁解锁)和Lock都是可重入锁(显式的,手动上锁就,解锁)
可重入锁:锁住的内容之间,无论嵌套多少,对加锁的对象都是开放的,对其他线程都是封闭的,递归锁
5.2.1synchronized演示
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();
public synchronized void add(){
add();
}
5.2.2Lock演示
ReentrantLock 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();
每一层上完锁,最后一定要执行锁
5.3死锁
多个线程执行过程中,因为争夺资源造成相互等待的现象,需要外部干涉才能继续执行下去
5.3.1死锁的原因
- 系统资源不足
- 进程运行推进顺序不合适
- 资源分配不当
5.3.2死锁的例子
new Thread(()->{
synchronized (a){
System.out.println(Thread.currentThread().getName()+"持有锁a,试图获取锁b");
synchronized (b){
System.out.println(Thread.currentThread().getName()+"获取锁b");
}
}
},"A").start();
new Thread(()->{
synchronized (b){
System.out.println(Thread.currentThread().getName()+"持有锁b,试图获取锁a");
synchronized (a){
System.out.println(Thread.currentThread().getName()+"获取锁a");
}
}
},"B").start();
互相等待
六、Callable&Future 接口
创建线程的多种方式:
- 继承Thread类
- 实现Runnable接口
- Callable接口
- 线程池的方式
6.1Callable 接口
线程有返回值
class Thread2 implements Callable{
@Override
public Object call() throws Exception {
return 200;
}
}
6.2Future 接口
- public boolean cancel(boolean mayInterrupt):
用于停止任务。如果尚未启动,它将停止任务。如果已启动,则仅在 mayInterrupt 为 true 时才会中断任务。 -
public Object get()抛出 InterruptedException,ExecutionException:用于获取任务的结果。如果任务完成,它将立即返回结果,否则将等待任务完成,然后返回结果。
-
public boolean isDone():
如果任务完成,则返回 true,否则返回 false可以看到 Callable 和 Future 做两件事,Callable 与 Runnable 类似,因为它封装了要在另一个线程上运行的任务,而 Future 用于存储从另一个线程获得的结果。实际上,future 也可以与 Runnable 一起使用。要创建线程,需要 Runnable。为了获得结果,需要 future
6.3FutureTask
Java 库具有具体的 FutureTask 类型,该类型实现 Runnable 和 Future,并方便地将两种功能组合在一起。 可以通过为其构造函数提供 Callable 来创建FutureTask。然后,将 FutureTask 对象提供给 Thread 的构造函数以创建Thread 对象。因此,间接地使用 Callable 创建线程
FutureTask<Integer> futureTask = new FutureTask<Integer>(new Thread2());
FutureTask<Integer> futureTask1=new FutureTask<>(()->{
return 1024;
});
- 当主线程将来需要时,就可以通过 Future 对象获得后台作业的计算结果或者执行状态
- 一般 FutureTask 多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
- 仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法
- 一旦计算完成,就不能再重新开始或取消计算
- get 方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常
- get 只计算一次,因此 get 方法放到最后
FutureTask<Integer> futureTask = new FutureTask<>(() -> 200);
new Thread(futureTask, "A").start();
FutureTask<Integer> futureTask1 = new FutureTask<>(() -> 1000);
new Thread(futureTask1, "B").start();
while (!futureTask.isDone()) {
System.out.println("A:wait...");
}
while (!futureTask1.isDone()) {
System.out.println("B:wait...");
}
//主线程汇总
System.out.println(futureTask.get());
System.out.println(futureTask1.get());
七、JUC 三大辅助类
7.1 减少计数 CountDownLatch
- CountDownLatch 主要有两个方法,当一个或多个线程调用 await 方法时,这些线程会阻塞
- 其它线程调用 countDown 方法会将计数器减 1(调用 countDown 方法的线程不会阻塞)
- 当计数器的值变为 0 时,因 await 方法阻塞的线程会被唤醒,继续执行
7.1.1demo
@Test
public void test01() {
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "号同学离开教师");
}, "" + i).start();
}
System.out.println(Thread.currentThread().getName()+"班长锁门");
}
人没走完班长就关门了
使用CountDownLatch
@Test
public void test02() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "号同学离开教室");
latch.countDown();
}, "" + i).start();
}
latch.await();
System.out.println(Thread.currentThread().getName()+"班长锁门");
}
7.2循环栅栏 CyclicBarrier
CyclicBarrier 看英文单词可以看出大概就是循环阻塞的意思,在使用中CyclicBarrier 的构造方法第一个参数是目标障碍数,每次执行 CyclicBarrier 一 次障碍数会加1,如果达到了目标障碍数,才会执行 cyclicBarrier.await()之后的语句。可以将 CyclicBarrier 理解为加 1 操作
7.2.1demo
private static final int NUMBER = 7;
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(NUMBER, () -> {
System.out.println("集齐龙珠");
});
for (int i = 1; i <= 7; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "龙珠被收集");
try {
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}, "" + i).start();
}
}
7.3 信号灯 Semaphore
Semaphore 的构造方法中传入的第一个参数是最大信号量(可以看成最大线程池),每个信号量初始化为一个最多只能分发一个许可证。使用 acquire 方 法获得许可证,release 方法释放许可
7.3.1demo
public static void main(String[] args) {
//设置许可数量
Semaphore semaphore = new Semaphore(3);
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();
}
},""+i).start();
}
}
八、读写锁
8.1 读写锁介绍
- 没有其他线程的写锁
-
没有写请求, 或者有写请求,但调用线程和持有锁的线程是同一个(可重入锁)
- 没有其他线程的读锁
- 没有其他线程的写锁
- 公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
- 重进入:读锁和写锁都支持线程重进入。
- 锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。
8.2 ReentrantReadWriteLock
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
/**
* 读锁
*/
private final ReentrantReadWriteLock.ReadLock readerLock;
/**
* 写锁
*/
private final ReentrantReadWriteLock.WriteLock writerLock;
final Sync sync;
/**
* 使用默认(非公平)的排序属性创建一个新的
* ReentrantReadWriteLock
*/
public ReentrantReadWriteLock() {
this(false);
}
/**
* 使用给定的公平策略创建一个新的 ReentrantReadWriteLock
*/
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
/**
* 返回用于写入操作的锁
*/
public ReentrantReadWriteLock.WriteLock writeLock() {
return writerLock;
}
/**
* 返回用于读取操作的锁
*/
public ReentrantReadWriteLock.ReadLock readLock() {
return readerLock;
}
abstract static class Sync extends AbstractQueuedSynchronizer {
}
static final class NonfairSync extends Sync {
}
static final class FairSync extends Sync {
}
public static class ReadLock implements Lock, java.io.Serializable {
}
public static class WriteLock implements Lock, java.io.Serializable {
}
}
8.3 入门案例
package com.ms.juc.demo1._8读写锁;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
//资源类
class Cache {
private volatile Map<String, Object> map = new HashMap<>();
//读写锁
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//添加
public void put(String key, Object value) {
//加写锁
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "正在写操作,key:" + key);
TimeUnit.SECONDS.sleep(1);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写完了,key:" + key);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放写锁
readWriteLock.writeLock().unlock();
}
}
//取数据
public Object get(String key) {
//加读锁
readWriteLock.readLock().lock();
Object res = null;
try {
System.out.println(Thread.currentThread().getName() + "读取key:" + key);
TimeUnit.SECONDS.sleep(1);
res = map.get(key);
System.out.println(Thread.currentThread().getName() + "取完了key:" + key);
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
//释放读锁
readWriteLock.readLock().unlock();
}
return res;
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) {
Cache cache = new Cache();
for (int i = 1; i <= 5; i++) {
int finalI = i;
new Thread(() -> {
cache.put("" + finalI, "" + finalI);
}, "线程" + i).start();
}
for (int i = 6; i <= 10; i++) {
int finalI = i;
new Thread(() -> {
cache.get("" + finalI);
}, "线程" + i).start();
}
}
}
8.4 小结(重要)
- 在线程持有读锁的情况下,该线程不能取得写锁(因为获取写锁的时候,如果发现当前的读锁被占用,就马上获取失败,不管读锁是不是被当前线程持有)。
-
在线程持有写锁的情况下,该线程可以继续获取读锁(获取读锁时如果发现写锁被占用,只有写锁没有被当前线程占用的情况才会获取失败)。
原因: 当线程获取读锁的时候,可能有其他线程同时也在持有读锁,因此不能把获取读锁的线程“升级”为写锁;而对于获得写锁的线程,它一定独占了读写锁,因此可以继续让它获取读锁,当它同时获取了写锁和读锁后,还可以先释放写锁继续持有读锁,这样一个写锁就“降级”为了读锁。
九、阻塞队列
当队列是空的,从队列中获取元素的操作将会被阻塞当队列是满的,从队列中添加元素的操作将会被阻塞
- 先进先出(FIFO):先插入的队列的元素也最先出队列,类似于排队的功能。从某种程度上来说这种队列也体现了一种公平性
- 后进先出(LIFO):后插入队列的元素最先出队列,这种队列优先处理最近发生的事件(栈)
9.1BlockingQueue
9.1.1方法
9.1.2demo
package com.ms.juc.demo1._9阻塞队列;
import org.junit.Test;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
public class BlockingQueueDemo {
@Test
public void test01() {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));//true
System.out.println(blockingQueue.add("b"));//true
System.out.println(blockingQueue.add("c"));//true
System.out.println(blockingQueue.element());//a
// System.out.println(blockingQueue.remove());//a
System.out.println(blockingQueue.add("d"));//抛出队列已满异常
}
@Test
public void test02() {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));//true
System.out.println(blockingQueue.offer("b"));//true
System.out.println(blockingQueue.offer("c"));//true
System.out.println(blockingQueue.offer("d"));//false
}
@Test
public void test03() throws InterruptedException {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
// blockingQueue.put("d");//被阻塞
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());//被阻塞
}
@Test
public void test04() throws InterruptedException {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));//true
System.out.println(blockingQueue.offer("b"));//true
System.out.println(blockingQueue.offer("c"));//true
System.out.println(blockingQueue.offer("d", 4, TimeUnit.SECONDS));//false
}
}
9.2常见的 BlockingQueue
9.2.1 ArrayBlockingQueue(常用)
一句话总结: 由数组结构组成的有界阻塞队列。
9.2.2 LinkedBlockingQueue(常用)
ArrayBlockingQueue 和 LinkedBlockingQueue 是两个最普通也是最常用 的阻塞队列,一般情况下,在处理多线程间的生产者消费者问题,使用这两个 类足以。一句话总结: 由链表结构组成的有界(但大小默认值为 integer.MAX_VALUE)阻塞队列。
9.2.3 DelayQueue
一句话总结: 使用优先级队列实现的延迟无界阻塞队列。
9.2.4 PriorityBlockingQueue
一句话总结: 支持优先级排序的无界阻塞队列。
9.2.5 SynchronousQueue
- 公平模式:SynchronousQueue 会采用公平锁,并配合一个 FIFO 队列来阻塞多余的生产者和消费者,从而体系整体的公平策略
- 非公平模式(SynchronousQueue 默认):SynchronousQueue 采用非公平锁,同时配合一个 LIFO 队列来管理多余的生产者和消费者,
- 而后一种模式,如果生产者和消费者的处理速度有差距,则很容易出现饥渴的情况,即可能有某些生产者或者是消费者的数据永远都得不到处理。
一句话总结: 不存储元素的阻塞队列,也即单个元素的队列。
9.2.6 LinkedTransferQueue
一句话总结: 由链表组成的无界阻塞队列。
9.2.7 LinkedBlockingDeque
- 插入元素时: 如果当前队列已满将会进入阻塞状态,一直等到队列有空的位置时再讲该元素插入,该操作可以通过设置超时参数,超时后返回 false 表示操作失败,也可以不设置超时参数一直阻塞,中断后抛出 InterruptedException 异常
- 读取元素时: 如果当前队列为空会阻塞住直到队列不为空然后返回元素,同样可以通过设置超时参数
一句话总结: 由链表组成的双向阻塞队列
十、线程池
10.1简介
- 降低资源消耗: 通过重复利用已创建的线程降低线程创建和销毁造成的销耗。
- 提高响应速度: 当任务到达时,任务可以不需要等待线程创建就能立即执行。
- 提高线程的可管理性: 线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
- Java 中的线程池是通过 Executor 框架实现的,该框架中用到了 Executor,Executors,ExecutorService,ThreadPoolExecutor 这几个类
10.2线程池的使用方式
10.2.1一池N线程
ExecutorService threadPool = Executors.newFixedThreadPool(5);
try {
for (int i = 0; i < 10; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "正在执行");
});
}
} finally {
threadPool.shutdown();
}
10.2.2一池一线程
ExecutorService threadPool = Executors.newSingleThreadExecutor();
try {
for (int i = 0; i < 10; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "正在执行");
});
}
} finally {
threadPool.shutdown();
}
10.2.3可扩容
ExecutorService threadPool = Executors.newCachedThreadPool();
try {
for (int i = 0; i < 10; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "正在执行");
});
}
} finally {
threadPool.shutdown();
}
10.3线程池参数
在工具类快速构造线程池的时候,都调用了同一个构造方法
ThreadPoolExecutor有七个参数
- corePoolSize 线程池的核心线程数,常驻线程数
- maximumPoolSize 能容纳的最大线程数
- keepAliveTime 空闲线程存活时间
- unit 存活的时间单位
- workQueue 存放提交但未执行任务的队列
- threadFactory 创建线程的工厂类
- handler 等待队列满后的拒绝策略
10.3.1 拒绝策略(重点)
10.4线程池底层工作原理(重要)
- 在创建了线程池后,线程池中的线程数为零
-
当调用 execute()方法添加一个请求任务时,线程池会做出如下判断:
2.1 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;2.2 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;2.3 如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;2.4 如果队列满了且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。 - 当一个线程完成任务时,它会从队列中取下一个任务来执行
- 当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:
4.1 如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。
4.2所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
10.5自定义线程池
实际中往往自己定义线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
2,
5,
2,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
try {
for (int i = 0; i < 10; i++) {
int finalI = i;
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"执行任务"+ finalI);
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
threadPool.shutdown();
}
十一、Fork/Join分支合并框架
11.1简介
- ForkJoinTask:
我们要使用 Fork/Join 框架,首先需要创建一个 ForkJoin 任务。该类提供了在任务中执行 fork 和 join 的机制。通常情况下我们不需要直接集成 ForkJoinTask 类,只需要继承它的子类,Fork/Join 框架提供了两个子类:
a.RecursiveAction:用于没有返回结果的任务
b.RecursiveTask:用于有返回结果的任务 - ForkJoinPool:
ForkJoinTask 需要通过 ForkJoinPool 来执行 - RecursiveTask:
继承后可以实现递归(自己调自己)调用的任务
11.2Fork 方法的实现原理
11.3join 方法
- 如果任务状态是已完成,则直接返回任务结果。
- 如果任务状态是被取消,则直接抛出 CancellationException
- 如果任务状态是抛出异常,则直接抛出对应的异常
- 首先通过查看任务的状态,看任务是否已经执行完成,如果执行完成,则直接返回任务状态;
- 如果没有执行完,则从任务数组里取出任务并执行。
- 如果任务顺利执行完成,则设置任务状态为 NORMAL,如果出现异常,则记录异常,并将任务状态设置为 EXCEPTIONAL。
11.4 Fork/Join 框架的异常处理
11.5案例
场景: 生成一个计算任务,计算 1+2+3.........+1000 , ==每 100 个数切分一个 子任务== 合并时差值超过10不能合并
package com.ms.juc.demo1._11分支合并;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
/*
生成一个计算任务,计算 1+2+3.........+1000,==每 100 个数切分一个子任务
合并时差值超过10不能合并
*/
class Task extends RecursiveTask<Integer> {
private static final Integer VALUE = 10;
private int begin;
private int end;
private int result;
public Task(int begin, int end) {
this.begin = begin;
this.end = end;
}
@Override
protected Integer compute() {
if (end - begin > VALUE) {
//直接计算
for (int i = begin; i <= end; i++) {
result += i;
}
} else {
//继续拆分
int mid = (begin + end) / 2;
//左边
Task task1 = new Task(begin, mid);
//右边
Task task2 = new Task(mid + 1, end);
//分支拆分
task1.fork();
task2.fork();
//合并结果
result=task1.join()+task2.join();
}
return result;
}
}
public class Demo {
public static void main(String[] args) throws Exception {
Task task = new Task(1, 100);
//创建分支合并池对象
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Integer> forkJoinTask = forkJoinPool.submit(task);
//获取合并之后的结果
System.out.println(forkJoinTask.get());
//关闭池对象
forkJoinPool.shutdown();
}
}
十二、异步调用
12.1CompletableFuture
12.1.1简介
12.1.2Future 与 CompletableFuture
- 不支持手动完成
我提交了一个任务,但是执行太慢了,我通过其他路径已经获取到了任务结果,现在没法把这个任务结果通知到正在执行的线程,所以必须主动取消或者一直等待它执行完成 - 不支持进一步的非阻塞调用
通过 Future 的 get 方法会一直阻塞到任务完成,但是想在获取任务之后执行额外的任务,因为 Future 不支持回调函数,所以无法实现这个功能 - 不支持链式调用
对于 Future 的执行结果,我们想继续传到下一个 Future 处理使用,从而形成一个链式的 pipline 调用,这在 Future 中是没法实现的。 - 不支持多个 Future 合并比如我们有 10 个 Future 并行执行,我们想在所有的 Future 运行完毕之后,执行某些函数,是没法通过 Future 实现的。
- 不支持异常处理
Future 的 API 没有任何的异常处理的 api,所以在异步运行时,如果出了问题是不好定位的。
12.2使用 CompletableFuture
12.2.1无返回值的异步任务
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName() + "----completableFuture");
});
completableFuture.get();
12.2.2有返回值的异步任务
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "----completableFuture");
return 1024;
});
Integer res = completableFuture.whenComplete((t, u) -> {
System.out.println("---t:" + t);//方法的返回值
System.out.println("---u:" + u);//异常信息
}).get();//也可以获取返回值
12.2.3线程依赖
当一个线程依赖另一个线程时,可以使用 thenApply 方法来把这两个线程串行化。
//先加10再平方
private static Integer num = 10;
public static void main(String[] args) throws Exception {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
num += 10;
return num;
}).thenApply(integer -> num * num);
Integer integer = future.get();
System.out.println(integer);
}
12.2.4消费处理结果
//消费处理结果
@Test
public void test03()throws Exception{
System.out.println("主线程开始");
CompletableFuture.supplyAsync(()->{
return 10;
}).thenApply(integer -> integer*integer)
.thenAccept(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println("主线程全部处理完成最后调用accept,结果为:"+integer);
}
});
}
12.2.5异常处理
//异常处理
@Test
public void test04()throws Exception{
System.out.println("主线程开始");
CompletableFuture<Integer> future=CompletableFuture.supplyAsync(()->{
int i=10/0;
return 10;
}).exceptionally(ex->{
ex.printStackTrace();
return -1;
});
System.out.println(future.get());//-1
}
12.2.6handle
@Test
public void test05()throws Exception{
System.out.println("主线程开始");
CompletableFuture<Integer> future=CompletableFuture.supplyAsync(()->{
return 10;
}).handle((i,ex)->{
if (ex!=null){
System.out.println("发生异常");
return -1;
}else {
System.out.println("未发生异常");
return i;
}
});
System.out.println(future.get());
}
12.2.7结果合并
@Test
public void test06()throws Exception{
System.out.println("主线程开始");
CompletableFuture<Integer> future=CompletableFuture.supplyAsync(()->{
System.out.println("任务1开始");
return 10;
});
//合并
CompletableFuture<Integer> future1=future.thenCompose(i->{
return CompletableFuture.supplyAsync(()->{
return i*i;
});
});
System.out.println(future.get());
System.out.println(future1.get());
}
thenCombine 合并两个没有依赖关系的 CompletableFutures 任务
@Test
public void test07() throws Exception {
CompletableFuture<Integer> job1 = CompletableFuture.supplyAsync(() -> {
return 10;
});
CompletableFuture<Integer> job2 = CompletableFuture.supplyAsync(() -> {
return 20;
});
//合并
CompletableFuture<Object> future = job1.thenCombine(job2, new BiFunction<Integer, Integer, List<Integer>>() {
@Override
public List<Integer> apply(Integer integer, Integer integer2) {
ArrayList<Integer> list = new ArrayList<>();
list.add(integer);
list.add(integer2);
return list;
}
}
);
System.out.println("合并结果:"+future.get());
}
@Test
public void test08() throws Exception {
System.out.println("主线程开始");
List<CompletableFuture> list = new ArrayList<>();
CompletableFuture<Integer> job1 = CompletableFuture.supplyAsync(() -> {
System.out.println("加 10 任务开始");
num += 10;
return num;
});
list.add(job1);
CompletableFuture<Integer> job2 = CompletableFuture.supplyAsync(() -> {
System.out.println("乘以 10 任务开始");
num = num * 10;
return num;
});
list.add(job2);
CompletableFuture<Integer> job3 = CompletableFuture.supplyAsync(() -> {
System.out.println("减以 10 任务开始");
num = num * 10;
return num;
});
list.add(job3);
CompletableFuture<Integer> job4 = CompletableFuture.supplyAsync(() -> {
System.out.println("除以 10 任务开始");
num = num * 10;
return num;
});
list.add(job4);
//多任务合并
List<Integer> collect =
list.stream().map(CompletableFuture<Integer>::join).collect(Collectors.toList());
System.out.println(collect);
}
@Test
public void test10() throws Exception {
System.out.println("主线程开始");
CompletableFuture<Integer>[] futures = new CompletableFuture[4];
CompletableFuture<Integer> job1 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(5000);
System.out.println("加 10 任务开始");
num += 10;
return num;
} catch (Exception e) {
return 0;
}
});
futures[0] = job1;
CompletableFuture<Integer> job2 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
System.out.println("乘以 10 任务开始");
num = num * 10;
return num;
} catch (Exception e) {
return 1;
}
});
futures[1] = job2;
CompletableFuture<Integer> job3 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(3000);
System.out.println("减以 10 任务开始");
num = num * 10;
return num;
} catch (Exception e) {
return 2;
}
});
futures[2] = job3;
CompletableFuture<Integer> job4 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(4000);
System.out.println("除以 10 任务开始");
num = num * 10;
return num;
} catch (Exception e) {
return 3;
}
});
futures[3] = job4;
CompletableFuture<Object> future = CompletableFuture.anyOf(futures);
System.out.println(future.get());
}