JUC并发编程—尚硅谷—2021-07-06

视频链接

一.:JUC 概述(1~4)

1:什么是 JUC:

   1)JUC 简介:
    -1:在 Java 中,线程部分是一个重点,本篇文章说的 JUC 也是关于线程的。
    -2:JUC 就是 java.util.concurrent 工具包的简称。
    -3:这是一个处理线程的工具包,jdk1.5 出现的。


2:线程 和 进程的概念:

   1)进程 & 线程:
    -1:指 在系统中,正在运行的一个应用程序;程序一旦运行就是进程。
(进程 — 资源分配的最小单位)

    -2:操作系统分配处理器时间资源的基本单元。或者说,进程之内独立执行的一个单元执行流。
(线程 — 程序执行的最小单位)。

   2)线程的状态:(流程图)
在这里插入图片描述
在这里插入图片描述

   3)wait & sleep:
    -1:相同:
  a:一旦执行方法,都可以使当前线程进入阻塞状态。
  b:他们都可以被 interrupted 方法中断。

    -2:不同:
  a:两个方法声明的位置不同:
sleep() --> Thread 类中声明的静态方法。
wait() --> Object 类中声明的方法,任何对象都可以调用。
  b:调用的范围不同:
sleep() --> 任意线程 的 任意位置 都可以调用。
wait() --> 必须使用在 同步方法 或 者同步代码块 中。
  c:关于是否释放同步监视器问题:
(如果两个方法都是用在,同步代码块 或者 同步方法中)
sleep() --> 不会释放同步锁,它也不会占锁。
wait() --> 释放同步锁

   4)并发 & 并行:
    -1:并发:一个 CPU 同时执行多个任务。(同一时刻多个线程访同一资源)
    -2:并行:多个 CPU 同时执行多个任务。(多个线程并行执行,之后再汇总)

   5)用户线程 和 守护线程:
    -1:用户线程:他们在几乎每个方面都是相同的,唯一的区别是 判断 JVM 何时离开。
(主线程结束了,用户线程还在运行,JVM 存活)

    -2:守护线程:
1、守护线程 是用来服务 用户线程的,通过 start() 方法前调用 thread.setDaemon(true); 此线程设置为守护线程。
2、当主线程退出,守护线程自动退出。
3、gc() 垃圾回收就是一个典型的守护线程。

   6)管程:(监视器)
在这里插入图片描述




二.:Lock 接口(5~7)

三.:线程间通信(8~11)

四.:线程间定制化通信(12~13)

五.:集合的线程安全(14~17)

六.:多线程锁(18~22)

七.:Callable 接口(23~25)

八.:JUC 强大的辅助类(26~28)

九.:可重入读写锁(ReentrantReadWriteLock) (29~32)

1:表锁 和 行锁:

   1)表锁:修改时,整张表上锁。
   2)行锁:只锁定修改的行。会发生死锁。(死锁:多个线程互相等待)

2:乐观锁 & 悲观锁:

   1)悲观锁:能解决并发中的各种问题;不支持并发操作,效率低。

   2)乐观锁:支持并发,效率高。(根据比对版本号是否相同,判断数据是否被修改过)
在这里插入图片描述


3:读写锁概述:(独占锁写 / 共享锁读)(互斥锁)

   1)读锁:共享锁,可并发读。
   2)写锁:独占锁,单独写。
   3)但是:不能同时存在 读 和 写 线程。(读写互斥,读读共享)
在这里插入图片描述

   4)缺点:
    -1:读锁 & 写锁,都会产生死锁。
    -2:容易造成 锁饥饿,一直读就没有写操作,
    -3:读的时候不能写,写的时候不能读。(自己线程可以读)
在这里插入图片描述


4:读写锁案例:

   1)集合读写操作方法:

class MyCache {

    /**
     * 创建 map 集合
     * 数据 不断发生变化 使用 volatile
     */
    public volatile static Map map = new HashMap();

    /**
     * 创建读写锁对象
     */
    private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

    // 放数据
    public void put(String key, Object value) {

        // 添加写锁
        rwLock.writeLock().lock();

        try {
            System.out.println(Thread.currentThread().getName() + ":正在写操作:" + key);
            // 暂停一会
            TimeUnit.SECONDS.sleep(2);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + ":写完了:" + key);
        } catch (InterruptedException e) {

        } finally {
            // 释放写锁
            rwLock.writeLock().unlock();
        }
    }

    // 取数据
    public Object get(String key) {

        // 添加读锁
        rwLock.readLock().lock();

        Object result = null;
        try {
            System.out.println(Thread.currentThread().getName() + "--正在读取操作--:" + key);
            // 暂停一会
            TimeUnit.SECONDS.sleep(2);
            result = map.get(key);
            System.out.println(Thread.currentThread().getName() + "--读取完了--:" + key);
        } catch (InterruptedException e) {
        } finally {
            // 释放读锁
            rwLock.readLock().unlock();
        }
        return result;
    }
}

   2)测试 读写锁:

/**
 * @author zhangxudong@chunyu.me
 * @date 2022/4/14 12:48 下午
 */
public class ReadWriteLockDemo {

    public static void main(String[] args) {
        MyCache myCache = new MyCache();

        // 多线程放数据
        for (int i = 0; i < 10; i++) {
        	// lamdb 表达式里的 变量,必须为 final 类型。
            final int num = i;
            new Thread(() -> {
                myCache.put(num + "", num + "");
            }, "ThreadName::" + i).start();
        }

        // 多线程取出数据
        for (int i = 0; i < 10; i++) {
            final int num = i;
            new Thread(() -> {
                myCache.get(num + "");
            }, "ThreadName::" + i).start();
        }
    }
}

   5)测试结果:(独占写,并发读)

ThreadName::0正在写操作:0
ThreadName::0写完了:0
ThreadName::1正在写操作:1
ThreadName::1写完了:1
ThreadName::2正在写操作:2

ThreadName::0--正在读取操作--0
ThreadName::1--正在读取操作--1
ThreadName::2--正在读取操作--2
ThreadName::1--读取完了--1
ThreadName::2--读取完了--2
ThreadName::0--读取完了--0



5:锁降级(学渣抄学霸作业)

   1)概念:将写入锁 降级为 读锁。(目的:提高数据的可见性)
(读锁 不能升级为 写锁)
(写的权限,高于读的权限)

   2)过程:(写锁 降级为 读锁)
在这里插入图片描述
   3)代码演示:

public static void main(String[] args) {

    // 创建可重入,读写锁对象
    ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    // 获取读锁
    ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
    // 获取写锁
    ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
    
     -- 锁降级过程 -- 
    // 1、获取写锁
    writeLock.lock();
    System.out.println("获取到了写锁");
    // 2、获取读锁
    readLock.lock();
    System.out.println("获取到了读锁");
    
    // 3、释放写锁
    writeLock.unlock();
    // 4、释放读锁
    readLock.unlock();
}





十.:阻塞队列(BlockingQueue)(33~35)

3:阻塞队列 用在哪里:

   1)生产者 消费者模式:
    -1:传统版:(1.0 -> 2.0 -> 3.0)
在这里插入图片描述
(while 不能改为 if,2个以上的多线程下,会控制不住)
(详见:https://blog.csdn.net/qq_43056248/article/details/109245417)

/**
 * @author zhangxudong@chunyu.me
 * @date 2022/4/16 3:31 下午
 */
public class MyBlockingQueue {
    int number = 0;

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    // 加法
    public void increment() {
        lock.lock();
        try {
            // 1:判断
            while (number != 0) {
                // 等待,不能生产
                condition.await();
            }
            // 2:干活
            number++;
            System.out.println(Thread.currentThread().getName() + "\t" + number);
            // 3:通知唤醒
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    // 减法
    public void decrement() {
        lock.lock();
        try {
            // 1:判断
            while (number == 0) {
                // 等待,不能生产
                condition.await();
            }
            // 2:干活
            number--;
            System.out.println(Thread.currentThread().getName() + "\t" + number);
            // 3:通知唤醒
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {

        MyBlockingQueue myBlockingQueue = new MyBlockingQueue();

        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                myBlockingQueue.increment();
            }, "AAA 生产一个:").start();
        }

        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                myBlockingQueue.decrement();
            }, "BBB 消费一个::").start();
        }
        System.out.println(myBlockingQueue.number);
    }
}

    -2:阻塞队列版:(3.0 版本)(高并发版本)

public class MyLock30 {

    public static void main(String[] args) throws InterruptedException {
    
        MyResource myResource = new MyResource(new ArrayBlockingQueue(3));
        
        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                try {
                    myResource.myProduct();
                } catch (Exception e) {
                }
            }, "product").start();
        }

        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                try {
                    myResource.myConsumer();
                } catch (Exception e) {
                }
            }, "consumer").start();
        }

        TimeUnit.SECONDS.sleep(5);
        myResource.stop();
    }
}
class MyResource {

    // 默认开启,进行生产 + 消费。(高并发)
    private volatile boolean FLAG = true;

    private AtomicInteger atomicInteger = new AtomicInteger();

    private BlockingQueue<String> blockingQueue = null;

    public MyResource(BlockingQueue blockingQueue) {
        this.blockingQueue = blockingQueue;
    }

    public void myProduct() throws Exception {
        String data = null;
        boolean offer;
        while (FLAG) {
            data = atomicInteger.getAndIncrement() + "";
            offer = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
            if (offer) {
                System.out.println(Thread.currentThread().getName() + ":生产队列成功。" + data);
            } else {
                System.out.println(Thread.currentThread().getName() + ":生产队列失败。" + data);
            }
            TimeUnit.SECONDS.sleep(1);
        }
        System.out.println(Thread.currentThread().getName() + ":生产者:停止插入队列:FLAG = false。");
    }

    public void myConsumer() throws Exception {
        String result = null;
        while (FLAG) {
            result = blockingQueue.poll(2, TimeUnit.SECONDS);
            if (result == null || result.equalsIgnoreCase("")) {
                FLAG = false;
                System.out.println(Thread.currentThread().getName() + ":消费队列接收数据:没有消息,停止" + result);
                return;
            }
            System.out.println(Thread.currentThread().getName() + ":消费队列接收数据:" + result);
        }
        System.out.println(Thread.currentThread().getName() + ":消费者:停止接收数据:FLAG = false");
    }

    public void stop() {
        FLAG = false;
    }
}

   2)线程池:
   3)消息中间件:




十一.:线程池(ThreadPool)(36~40):

1:线程池 概述

   1)线程池简介:
    -1:一种线程使用模式。
    -2:线程过多,会带来调度开销,进而影响( 缓存局部性 和 整体性能 )
    -3:而线程池,维护着多个线程。等待着监督管理者,分配可并发执行的任务。
    -4:这避免了,在处理短时间任务时,创建与销毁线程的代价,
    -5:线程池不仅能够保证内核的充分利用,还能防止过分调度。

   2)线程池 优势 & 特点:
    -1:线程池做的工作主要是,控制运行的线程数量。
    -2:处理过程中,将任务放入队列,然后在线程创建后启动这些任务。
    -3:如果 线程数量超过了最大数量,超出数量的线程,排队等候,
    -4:等其他线程执行完毕,再从队列中取出任务来执行。
在这里插入图片描述

   3)它的主要特点为:
    -1:降低资源消耗:减少 反复创建、销毁线程,所带来的消耗。
    -2:提高响应速度:不需要等待创建线程的过程,可使用创建好的线程,立即执行。
    -3:提高线程的可管理性:使用线程池,对线程进行统一的(分配、调优 和 监控)。


2:线程池 架构:

   1)Java 中的线程池 通过 Executor 框架实现,该框架中用到了:下面这几个类
    -1:Executor:
    -2:Executors:
    -3:ExecutorService:
    -4:ThreadPoolExecutor:

   2)架构图:
在这里插入图片描述


3:线程池 使用方式:(关闭资源很重要)

   1)一池 N 线程:

public static void main(String[] args) {
 
    // 5个线程的线程池
    ExecutorService executor = Executors.newFixedThreadPool(5);
 
    try {
        // 同时处理 100 个请求。
        for (int i = 0; i < 100; i++) {
            executor.execute(() -> System.out.println(Thread.currentThread().getName() + ":处理业务:"));
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        executor.shutdown();
    }
}

   2)一池一线程:(一个任务一个任务的执行)

Executors.newSingleThreadExecutor();

   3)线程池根据需求创建线程:(可扩容,遇强则强)

Executors.newCachedThreadPool();



4:线程池 底层创建方式:

( LinkedBlockingQueue )
在这里插入图片描述


5:线程池 7 个参数:

   1)创建线程池 底层方法:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {

   2)参数解读:
-1:corePoolSize: 线程池中,常驻的核心线程数。。【 创建好以后,一直存在,除非设置了allowCoreThreadTimeOut 】

-2:maximumPoolSize:线程池中,允许的同时执行的,最大线程数。

-3:keepAliveTime:多余的空闲线程,的存活时间。。【 当线程数大于核心数时,只要线程空闲时间,大于指定的 存活时间,就会释放空闲(核心线程之外的)的线程。】
-4:unit:keepAliveTime 参数的时间单位。

-5:workQueue:任务队列:存储被提交,但尚未被执行的任务。(本质是:阻塞队列)。。【 如果任务有很多,就会将 目前多的任务,放在队列里面。只要有线程空闲,就会去队列取出新的任务去执行。】

-6:threadFactory:创建 线程池中,工作线程的线程工厂。(一般默认即可)
-7:handler:拒绝策略。。如果队列满了,并且工作线程,等于允许最大线程数。。【按照 我们指定的 拒绝策略,拒绝执行任务】。
在这里插入图片描述


6:线程池 底层工作流程:

   1)文字流程:
-1:线程池创建,准备好 核心数量的 核心线程,准备接受任务。
-2:core 满了,就将 再进来的 任务 放入到 阻塞队列中。空闲的 core 就会自己去 阻塞队列 获取任务执行。

-3:阻塞队列满了,就直接开新的线程,最大能开到 max 指定的数量。
-4:max 满了,就是用 RejectedExecutionHandler 拒绝策略来拒绝任务。

-5:max 都执行完成,有很多空闲线程,在指定时间, keepAliveTime 以后,释放 mac-core 这些线程。
在这里插入图片描述

   2)问:一个线程池,core 7,max 20,queue 50 ,100并发进来,如何分配?:
-1:7 个会立即执行,50个进入队列,然后开辟 13 个新线程,剩余 30 个,使用拒绝策略进行拒绝,
-2:都执行完毕后,超过时间会关闭新开启的13 个临时线程。

   3)工作流程图示:
在这里插入图片描述

   4)JDK 内置的拒绝策略:(4种)
(队列满了 且 达到最大线程数量,使用拒绝策略)
(4 种拒绝策略,都实现了:RejectedExecutionHandler:接口)
在这里插入图片描述


7:自定义 线程池:

   1)约定:
在这里插入图片描述

   2)自定义线程池创建 :(工作中使用方式) (此定义参数:最多处理 8 个线程)

public static void main(String[] args) {
    /**
     * public ThreadPoolExecutor(int corePoolSize,
     *                           int maximumPoolSize,
     *                           long keepAliveTime,
     *                           TimeUnit unit,
     *                           BlockingQueue<Runnable> workQueue,
     *                           ThreadFactory threadFactory,
     *                           RejectedExecutionHandler handler) {
     */
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            2,
            3,
            1L,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(3),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());
}



8:生产环境,线程池参数配置:

   1)工作中如何使用线程池:( new ThreadPoolExecutor(); )

   2)如何自定义线程池参数:

// 查看 CPU 核数
System.out.println(Runtime.getRuntime().availableProcessors());

(核心线程数是 0 ,最大线程数计算如下:)
    -1:CPU 密集型:
在这里插入图片描述
    -2:IO 密集型:
在这里插入图片描述




十二.:分支合并框架(Fork / Join)(41~42):

1:Fork / Join 框架简介:

   1)(Fork / Join)(分支 / 合并):它可以将一个大的任务,拆分成多个子任务,进行并行处理。

   2)最后将子任务结果合并成最后的计算结果,并进行输出:

   3)Fork / Join 框架要完成两件事情::
    -1:Fork:把一个复杂的任务,进行拆分,大事化小。
    -2:Join:把 拆分任务的结果进行合并。

   4)示意图:
在这里插入图片描述


2:Fork 方法:

在这里插入图片描述

   1)递归任务:继承后,可以实现递归调用的任务。

   2)java 手册说明:
在这里插入图片描述

   3)代码实现:(略)




十三.:异步回调(CompletableFuture)(43~43):

参照谷粒商城:https://www.yuque.com/aiyou-ywqry/xp148m/hgwlm7zp1vmdhkgp

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值