《JUC并发编程 - 基础篇》 Callable接口 | 辅助类 | 读写锁 | 阻塞队列 | 线程池 | Stream流 | 分支合并框架

📒博客首页:热爱编程的大李子 📒

🌞文章目的:Callable接口 | 辅助类 | 读写锁 | 阻塞队列 | 线程池 | Stream流 | 分支合并框架 总结🌞

🍄参考视频:尚硅谷2020权威教程JUC🍄

🙏博主在学习阶段,如若发现问题,请告知,非常感谢🙏

💙同时也非常感谢各位小伙伴们的支持💙

🌈每日一语:我生来就是高山而非溪流 ,我欲于群峰之巅俯视平庸的沟壑。我生来就是人杰而非草芥,我站在伟人之肩藐视卑微的懦夫!🌈

💗感谢: 我只是站在巨人们的肩膀上整理本篇文章,感谢走在前路的大佬们💗

🌟最后,祝大家每天进步亿点点! 欢迎大家点赞👍➕收藏⭐️➕评论💬支持博主🤞!🌟

在这里插入图片描述



7、Callable接口

引入: 面试题:获得多线程的方法几种?

(1)继承thread类(2)runnable接口
如果只回答这两个你连被问到juc的机会都没有

(3) 实现Callable接口

7.1 是什么?

image-20220429173819107

这是一个函数式接口,因此可以用作lambda表达式或方法引用的赋值对象。

7.2 与runnable对比

实现方法对比:

//创建新类MyThread实现runnable接口
class MyThread implements Runnable{
     @Override
     public void run() {

     }
}
//新类MyThread2实现callable接口
class MyThread2 implements Callable<Integer>{
     @Override
     public Integer call() throws Exception {
      return 200;
     } 
}

面试题:callable接口与runnable接口的区别?

答:(1)是否有返回值
(2)是否抛异常
(3)落地方法不一样,一个是run,一个是call

7.3 怎么用

直接替换runnable是否可行?

不可行,因为:thread类的构造方法根本没有Callable

image-20220429174138041

解决办法:认识不同的人找中间人

这像认识一个不认识的同学,我可以找中间人介绍。
中间人是什么?java多态,一个类可以实现多个接口!!

image-20220429174235784

运行成功后如何获得返回值?ft.get();

image-20220429174313289

实现代码

class MyThread implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("*********come in here");
        return 1024;
    }
}


/**
 * @author lxy
 * @version 1.0
 * @Description 多线程中,第三种获得多线程的方式
 * @date 2022/4/29 16:55
 */
public class CallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask <Integer> futureTask = new FutureTask <>(new MyThread());
        new Thread(futureTask,"A").start();//只被执行一次.
        new Thread(futureTask,"B").start();

        System.out.println(Thread.currentThread().getName());
        System.out.println(futureTask.get());
    }
}

7.4 FutureTask

7.4.1 FutureTask原理

未来的任务,用它就干一件事,异步调用
main方法就像一个冰糖葫芦,一个个方法由main串起来。
但解决不了一个问题:正常调用挂起堵塞问题 , 比如一个方法执行起来很花费时间…我们可以将它单独另启动一个线程A去做,主线程继续往下执行,最终在主线程结束之前将A的结果再进行操作… 可以大大提高效率.

image-20220429174508708

例子:
(1)老师上着课,口渴了,去买水不合适,讲课线程继续,我可以单起个线程找班长帮忙买水,
水买回来了放桌上,我需要的时候再去get。
(2)4个同学,A算1+20,B算21+30,C算31*到40,D算41+50,是不是C的计算量有点大啊,
FutureTask单起个线程给C计算,我先汇总ABD,最后等C计算完了再汇总C,拿到最终结果
(3)高考:会做的先做,不会的放在后面做

7.4.2 原理补充
  1. 在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成,当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态。
  2. 一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
  3. 如果计算尚未完成,则阻塞 get 方法。一旦计算完成,就不能再重新开始或取消计算。get方法获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常。
  4. 只计算一次(线程只能被创建执行一次),get方法放到最后

8、JUC强大的辅助类讲解

8.1 CountDownLatch(减少计数)

/**
 *
 * CountDownLatch: 让一些线程阻塞直到另一些线程完成一系列操作后才被唤醒。
 *
 * CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞。
 * 其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞),
 * 当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行。
 *
 * 解释:6个同学陆续离开教室后值班同学才可以关门。
 *
 * main主线程必须要等前面6个线程完成全部工作后,自己才能开干
 */
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);//给计数器赋值为6
        for (int i = 0; i < 6; i++) {//有六个上自习的同学,各自离开教室的时间不一样
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"\t号同学离开教室");
                countDownLatch.countDown();//每调用一次,计数器减一(也就是一个线程退出)
            },String.valueOf(i)).start();
        }

        countDownLatch.await();//阻塞main线程,当计数器减为0,则唤醒main线程.

        System.out.println(Thread.currentThread().getName()+"\t************班长关门走人,main线程是班长");
    }
}

8.2 CyclicBarrier(循环栅栏)

/**
 * CyclicBarrier:字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是,
 * 让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有
 * 被屏障拦截的线程才会继续干活。
 * 线程进入屏障通过CyclicBarrier的await()方法。
 *
 * 例:集齐7颗龙珠就可以召唤神龙
 */
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        //CyclicBarrier(int parties, Runnable barrierAction) 当所有的屏障经历完才干活
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("**********召唤神龙");
        });

        for (int i = 0; i < 7; i++) {
            final int tempInt = i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"\t收集到第"+tempInt+"颗龙珠");
                try {
                    cyclicBarrier.await();//进行等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}

8.3 Semaphore 信号灯

/**
 *
 * @Description: 信号量
 *
 * 在信号量上我们定义两种操作:
 * acquire(获取) 当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1),
 *             要么一直等下去,直到有线程释放信号量,或超时。
 * release(释放)实际上会将信号量的值加1,然后唤醒等待的线程。
 *
 * 信号量主要用于两个目的,一个是用于多个共享资源的互斥使用(资源被用尽,只能等待,直到有多余资源),
 * 另一个用于并发线程数的控制。(1s 100w访问量)
 * 例:抢车位
 */
public class SemaphoreDemo {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);//资源类,有三个空车位
        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                try {
                    semaphore.acquire();//占车位,车位数-1
                    System.out.println(Thread.currentThread().getName()+"\t抢占到了车位");
                    //暂停一会线程
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println(Thread.currentThread().getName()+"\t离开了车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();//车位数+1
                }
            },String.valueOf(i)).start();
        }
    }
}

9、ReentrantReadWriteLock

9.1 读写锁介绍

现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那么频繁。在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作了。

针对这种场景,JAVA 的并发包提供了读写锁ReentrantReadWriteLock ,它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁。

线程进入读锁的前提条件:

  • 没有其他线程的写锁
  • 没有写请求, 或者有写请求,但调用线程和持有锁的线程是同一个(可重入锁)。

线程进入写锁的前提条件:

  • 没有其他线程的读锁
  • 没有其他线程的写锁

而读写锁有以下三个重要的特性:
(1)公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
(2)重进入:读锁和写锁都支持线程重进入。
(3)锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。

9.2 ReentrantReadWriteLock

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 {
    }
}

可以看到,ReentrantReadWriteLock 实现了ReadWriteLock 接口,ReadWriteLock 接口定义了获取读锁和写锁的规范,具体需要实现类去实现;同时其还实现了Serializable 接口,表示可以进行序列化,在源代码中可以看到ReentrantReadWriteLock 实现了自己的序列化逻辑。

9.3 入门案例

场景: 使用ReentrantReadWriteLock 对一个hashmap 进行读和写操作

package com.rg.juc;


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 MyCache {
    private volatile Map <String, Object> map = new HashMap <>();

    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public void put(String key, Object value) {
        readWriteLock.writeLock().lock();//上写锁
        try {
            System.out.println(Thread.currentThread().getName() + "\t ----写入数据" + key);
            //网络拥堵(暂停一会线程)
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "\t ----写入完成"+key);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }

    public void get(String key) {
        readWriteLock.readLock().lock();//读锁
        try {
            System.out.println(Thread.currentThread().getName() + "\t ----读取数据" + key);
            //网络拥堵(暂停一会线程)
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Object result = map.get(key);
            System.out.println(Thread.currentThread().getName() + "\t ----读取完成" + result);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }

    }
}

/**
 * @author lxy
 * @version 1.0
 * @Description
 * @date 2022/5/1 11:59
 */
public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        //五个读线程
        for (int i = 1; i <= 5; i++) {
            final int tempInt = i;
            new Thread(() -> {
                myCache.put(tempInt + "", tempInt + "");
            }, String.valueOf(i)).start();
        }
        //五个写线程
        for (int i = 1; i <= 5; i++) {
            final int tempInt = i;
            new Thread(() -> {
                myCache.get(tempInt + "");
            }, String.valueOf(i)).start();
        }
    }
}

9.4 小结(重要)

  • 在线程持有读锁的情况下,该线程不能取得写锁(因为获取写锁的时候,如果发现当前的读锁被占用,就马上获取失败,不管读锁是不是被当前线程持有)。
  • 在线程持有写锁的情况下,该线程可以继续获取读锁(获取读锁时如果发现写锁被占用,只有写锁没有被当前线程占用的情况才会获取失败)。

原因: 当线程获取读锁的时候,可能有其他线程同时也在持有读锁,因此不能把获取读锁的线程“升级”为写锁;而对于获得写锁的线程,它一定独占了读写锁,因此可以继续让它获取读锁,当它同时获取了写锁和读锁后,还可以先释放写锁继续持有读锁,这样一个写锁就“降级”为了读锁。

10 阻塞队列

10.1 BlockingQueue 简介

Concurrent 包中,BlockingQueue 很好的解决了多线程中,如何高效安全“传输”数据的问题。通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利。本文详细介绍BlockingQueue 家庭中的所有成员,包括他们各自的功能以及常见使用场景。

阻塞队列,顾名思义,首先它是一个队列, 通过一个共享的队列,可以使得数据由队列的一端输入,从另外一端输出;

image-20220505210754619

  • 当队列是空的,从队列中获取元素的操作将会被阻塞
  • 当队列是满的,从队列中添加元素的操作将会被阻塞
  • 试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素
  • 试图向已满的队列中添加新元素的线程将会被阻塞,直到其他线程从队列中移除一个或多个元素或者完全清空,使队列变得空闲起来并后续新增.

在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤起

为什么需要BlockingQueue ?

好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了

在concurrent包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。

10.2 阻塞队列种类分析

架构介绍

image-20220505212252623

种类分析

  • ArrayBlockingQueue:由数组结构组成的有界阻塞队列。
  • LinkedBlockingQueue:由链表结构组成的有界(但大小默认值为integer.MAX_VALUE)阻塞队列
  • PriorityBlockingQueue:支持优先级排序的无界阻塞队列。
  • DelayQueue:使用优先级队列实现的延迟无界阻塞队列。
  • SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列。
  • LinkedTransferQueue:由链表组成的无界阻塞队列。
  • LinkedBlockingDeque:由链表组成的双向阻塞队列。

10.3 BlockingQueue 核心方法

image-20220505211243600

image-20220505211526868

10.4 入门案例

package com.rg.juc;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * @author lxy
 * @version 1.0
 * @Description 阻塞队列
 * @date 2022/5/3 16:32
 */
public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue<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("x"));//IllegalStateException: Queue full
        //
        // System.out.println(blockingQueue.remove());
        // System.out.println(blockingQueue.remove());
        // System.out.println(blockingQueue.remove());
        // // System.out.println(blockingQueue.remove());//NoSuchElementException

        //第二组
        /*System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));
        // System.out.println(blockingQueue.offer("x"));//false

        // System.out.println(blockingQueue.peek());//如果有元素则返回队头元素,如果没有则返回NULL

        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());//null*/

        //第三组  没有返回值
        /*blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");
        // blockingQueue.put("x");//一直阻塞等待...

        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("x",3L, TimeUnit.SECONDS));

        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll(3L,TimeUnit.SECONDS));
    }
}

11、ThreadPool线程池

11.1 为什么用线程池

例子:

  • 10年前单核CPU电脑,假的多线程,像马戏团小丑玩多个球,CPU需要来回切换。
  • 现在是多核电脑,多个线程各自跑在独立的CPU上,不用切换效率高。

线程池的优势:
线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕, 再从队列中取出任务来执行。

它的主要特点为:线程复用;控制最大并发数;管理线程。

第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的销耗。
第二:提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

11.2 线程池如何使用

11.2.1 架构说明

Java中的线程池是通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类 Executors是操作线程的工具类

image-20220505212955100

11.2.2 线程池分类
  • Executors.newFixedThreadPool(int): 执行长期任务性能好,创建一个线程池,一池有N个固定的线程,有固定线程数的线程
public static ExecutorService newFixedThreadPool(int nThreads) {
	 return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
// newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的是LinkedBlockingQueue
  • Executors.newSingleThreadExecutor() : 一个任务一个任务的执行,一池一线程
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
}
// newSingleThreadExecutor 创建的线程池corePoolSize和maximumPoolSize值都是1,它使用的是LinkedBlockingQueue
  • Executors.newCachedThreadPool() :执行很多短期异步任务,线程池根据需要创建新线程,但在先前构建的线程可用时将重用它们。可扩容,遇强则强
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}
// newCachedThreadPool创建的线程池将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,它使用的是SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。
11.2.3 ThreadPoolExecutor底层原理

image-20220505213912047

11.3 入门案例

package com.rg.juc;

import java.util.concurrent.*;

/**
 * @author lxy
 * @version 1.0
 * @Description 银行开放窗口,顾客办理业务
 * @date 2022/5/3 21:58
 */
public class MyThreadPoolDemo {
    public static void main(String[] args) {
        initPool();
    }

    private static void initPool() {
        // ExecutorService threadPool = Executors.newFixedThreadPool(5);//一池五个工作线程,类似一个银行有5个受理窗口
        // ExecutorService threadPool = Executors.newSingleThreadExecutor();//一池1个工作线程,类似一个银行一个受理窗口
        ExecutorService threadPool = Executors.newCachedThreadPool();//一池N个工作线程,类似一个银行有N个受理窗口


        try{
            //模拟有10个顾客来银行办理业务,目前池子里面有5个工作人员提供服务
            for (int i = 1; i <= 10; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"\t 办理业务");
                });
                //暂停线程 ,线程会变得逐渐有序..
                // try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            threadPool.shutdown();//释放线程池
        }
    }
}

11.4 线程池七大参数

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.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

1、corePoolSize:线程池中的常驻核心线程数 (也就是当前线程数)

2、maximumPoolSize:线程池中能够容纳同时执行的最大线程数,此值必须大于等于1

3、keepAliveTime:多余的空闲线程的存活时间当前池中线程数量超过corePoolSize时,当空闲时间
达到keepAliveTime时,多余线程会被销毁直到只剩下corePoolSize个线程为止

4、unit:keepAliveTime的单位

5、workQueue:任务队列,被提交但尚未被执行的任务

6、threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程,一般默认的即可

7、handler:拒绝策略,表示当队列满了,并且工作线程大于等于线程池的最大线程(maximumPoolSize)时如何来拒绝请求执行的runnable的策略

11.5 线程池底层工作原理

image-20220505214458404

image-20220505215259126

分析

1、在创建了线程池后,开始等待请求。
2、当调用execute()方法添加一个请求任务时,线程池会做出如下判断:
2.1如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
2.2如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列
2.3如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
2.4如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
3、当一个线程完成任务时,它会从队列中取下一个任务来执行。
4、当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:
如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。
所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小

11.6 线程池的拒绝策略

11.6.1 线程池是什么

等待队列已经排满了,再也塞不下新任务了

同时,线程池中的max线程也达到了,无法继续为新任务服务。

这个是时候我们就需要拒绝策略机制合理的处理这个问题。

11.6.2 JDK内置的拒绝策略
  • AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行
  • CallerRunsPolicy:“调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
  • DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加人队列中. 尝试再次提交当前任务。也就是抛弃最先入队列还没处理的几个。
  • DiscardPolicy:该策略默默地丢弃无法处理的任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种策略。

以上内置拒绝策略均实现了 RejectedExecutionHandle 接口

11.7 工作中线程池用哪个?

在工作中单一的/固定数的/可变的三种创建线程池的方法哪个用的多?超级大坑

答案是一个都不用,我们工作中只能使用自定义的

Executors中JDK已经给你提供了,为什么不用?

image-20220505221348156

自定义线程池代码

public class MyThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                5,
                2L,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque <>(3),
                Executors.defaultThreadFactory(),
                //new ThreadPoolExecutor.AbortPolicy()
                //new ThreadPoolExecutor.CallerRunsPolicy()
                //new ThreadPoolExecutor.DiscardOldestPolicy()
                new ThreadPoolExecutor.DiscardPolicy());


        try{
            //模拟有10个顾客来银行办理业务,目前池子里面有5个工作人员提供服务
            for (int i = 1; i <= 9; i++) {
                final int tempInt = i;
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"\t 办理业务"+tempInt);
                });
                //暂停线程 ,线程会变得逐渐有序..
                // try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            threadPool.shutdown();//释放线程池
        }

    }
}

12、Java8之流式计算复习

12.1 函数式接口

image-20220505221737523

java内置核心四大函数式接口

image-20220505221757638

如何使用

//R apply(T t);函数型接口,一个参数,一个返回值
Function<String,Integer> function = t ->{return t.length();};
System.out.println(function.apply("abcd"));

//boolean test(T t);断定型接口,一个参数,返回boolean
Predicate<String> predicate = t->{return t.startsWith("a");};
System.out.println(predicate.test("a"));

// void accept(T t);消费型接口,一个参数,没有返回值
Consumer<String> consumer = t->{
    System.out.println(t);
};
consumer.accept("javaXXXX");

//T get(); 供给型接口,无参数,有返回值
Supplier<String> supplier =()->{return UUID.randomUUID().toString();};
System.out.println(supplier.get());
private static void testFunction() {
    // Function<String,Integer> function = new Function <String, Integer>() {
    //     @Override
    //     public Integer apply(String s) {
    //         return s.length();
    //     }
    // };
    //R apply(T t);函数型接口,一个参数,一个返回值
    Function <String,Integer> function = s->{ return s.length(); };
    System.out.println(function.apply("abc"));

    //boolean test(T t);断定型接口,一个参数,返回boolean
    // Predicate<String> predicate = new Predicate <String>() {
    //     @Override
    //     public boolean test(String s) {
    //         return s.isEmpty();
    //     }
    // };
    Predicate <String> predicate = s->{return s.isEmpty();};
    System.out.println(predicate.test("abc"));

    // void accept(T t);消费型接口,一个参数,没有返回值
    // Consumer<String> consumer = new Consumer <String>() {
    //     @Override
    //     public void accept(String s) {
    //         System.out.println("I love Java!");
    //     }
    // };
    Consumer <String> consumer = s -> {System.out.println("I love Java!"); };
    consumer.accept("java");

    //T get(); 供给型接口,无参数,有返回值
    // Supplier<String> supplier = new Supplier <String>() {
    //     @Override
    //     public String get() {
    //         return "Java";
    //     }
    // };

    Supplier <String> supplier = ()->{
        return "Java";
    };
    System.out.println(supplier.get());
}

12.2 Stream流

12.2.1 是什么?

流(Stream) 到底是什么呢?
数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
“集合讲的是数据,流讲的是计算!”

12.2.2 特点
  • Stream 自己不会存储元素
  • Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
  • Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
12.2.3 如何用

1.创建一个Stream:一个数据源(数组、集合)

2.中间操作:一个中间操作,处理数据源数据

3.终止操作:一个终止操作,执行中间操作链,产生结果

类比: 源头=>中间流水线=>结果

12.2.4 代码演示
package com.rg.juc;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.sql.SQLOutput;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
 * @author lxy
 * @version 1.0
 * @Description
 * @date 2022/5/4 20:51
 */

@Data
@NoArgsConstructor
@AllArgsConstructor
class User
{
    private Integer id;
    private String  userName;
    private int     age;
}

/**
 * @create 2019-02-26 22:24
 *
 * 题目:请按照给出数据,找出同时满足
 *      偶数ID且年龄大于24且用户名转为大写且用户名字母倒排序
 *      最后只输出一个用户名字
 */
public class StreamDemo {
    public static void main(String[] args) {
        User u1 = new User(11, "a", 23);
        User u2 = new User(12, "b", 24);
        User u3 = new User(13, "c", 22);
        User u4 = new User(14, "d", 28);
        User u5 = new User(16, "e", 26);

        List <User> list = Arrays.asList(u1, u2, u3, u4, u5);

        list.stream().filter(u->{
            return u.getId() % 2 ==0;
        }).filter(t->{
            return t.getAge() > 24;
        }).map(m->{
            return m.getUserName().toUpperCase();
        }).sorted((o1,o2)->{
            return o2.compareTo(o1);
        }).forEach(System.out::println);

    }

    private static void testFunction() {
        // Function<String,Integer> function = new Function <String, Integer>() {
        //     @Override
        //     public Integer apply(String s) {
        //         return s.length();
        //     }
        // };
        //R apply(T t);函数型接口,一个参数,一个返回值
        Function <String,Integer> function = s->{ return s.length(); };
        System.out.println(function.apply("abc"));

        //boolean test(T t);断定型接口,一个参数,返回boolean
        // Predicate<String> predicate = new Predicate <String>() {
        //     @Override
        //     public boolean test(String s) {
        //         return s.isEmpty();
        //     }
        // };
        Predicate <String> predicate = s->{return s.isEmpty();};
        System.out.println(predicate.test("abc"));

        // void accept(T t);消费型接口,一个参数,没有返回值
        // Consumer<String> consumer = new Consumer <String>() {
        //     @Override
        //     public void accept(String s) {
        //         System.out.println("I love Java!");
        //     }
        // };
        Consumer <String> consumer = s -> {System.out.println("I love Java!"); };
        consumer.accept("java");

        //T get(); 供给型接口,无参数,有返回值
        // Supplier<String> supplier = new Supplier <String>() {
        //     @Override
        //     public String get() {
        //         return "Java";
        //     }
        // };

        Supplier <String> supplier = ()->{
            return "Java";
        };
        System.out.println(supplier.get());
    }
}

13、分支合并框架

13.1 原理

Fork:把一个复杂任务进行分拆,大事化小
Join:把分拆任务的结果进行合并

image-20220506092227206

image-20220506092246821

13.2 相关类

ForkJoinPool

image-20220506092321432

分支合并池 类比=> 线程池

ForkJoinTask

image-20220506092348480

ForkJoinTask 类比=> FutureTask

RecursiveTask

image-20220506092419194

递归任务:继承后可以实现递归(自己调自己)调用的任务

 class Fibonacci extends RecursiveTask<Integer> {
   final int n;
   Fibonacci(int n) { this.n = n; }
   Integer compute() {
     if (n <= 1)
       return n;
     Fibonacci f1 = new Fibonacci(n - 1);
     f1.fork();
     Fibonacci f2 = new Fibonacci(n - 2);
     return f2.compute() + f1.join();
   }
 }

13.3 入门案例

/**
 * @author lxy
 * @version 1.0
 * @Description  分支合并案例
 * @date 2022/5/5 17:07
 */
class MyTask extends RecursiveTask<Integer> {//RecursiveTask:递归任务

    private static final Integer ADJUST_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() {
        if(end-begin <= ADJUST_VALUE){
            for (int i = begin; i <= end; i++) {
                result = result + i;
            }
        }else{
            int middle = (end + begin) / 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 = new MyTask(0, 10);
        ForkJoinPool threadPool = new ForkJoinPool();
        ForkJoinTask <Integer> forkJoinTask = threadPool.submit(myTask);
        System.out.println(forkJoinTask.get());
        threadPool.shutdown();//关闭线程池
    }
}

14、异步回调

14.1 原理

image-20220506092628724

14.2 入门案例

/**
 * @author lxy
 * @version 1.0
 * @Description 同步,异步,异步回调 案例
 * @date 2022/5/5 18:18
 */
public class CompletableFutureDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //同步,异步,异步回调
        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
            System.out.println(Thread.currentThread().getName()+"没有返回,update mysql ok");
        });
        completableFuture.get();

        //异步回调
        CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()+"\t completableFuture2");
            int age = 10 / 0;
            return 1024;
        });

        completableFuture2.whenComplete((t,u)->{
            System.out.println("------t="+t);
            System.out.println("------u="+u);;
        }).exceptionally(f->{//t是正常结果 u和f都是异常信息
            System.out.println("------exception:"+f.getMessage());
            return 444;
        }).get();
    }
}

总结

OK,今天关于 JUC的知识分享 就到这里,希望本篇文章能够帮助到大家,同时也希望大家看后能学有所获!!!

后序博主忙完找暑假实习,将对JUC并发进行深入的学习和分享,欢迎大家继续观看我的JUC并发编程专栏!

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱编程的大李子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值