尚硅谷宋红康2021JUC

一. JUC概述

  1. 什么是JUC?

    (1)java并发编程包中的一些工具类,这些工具类可以更加方便实现并发编程操作。

    (2)JUC 就是 java.util .concurrent 工具包的简称。这是一个处理线程的工具包,JDK 1.5 开始出现的。

  2. 线程和进程的区别?

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

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

  3. 并发和并行的概念区别?

    并发:同一时刻多个线程在访问同一个资源,多个线程对一个点 例子:春运抢票 电商秒杀…

    并行:多项工作一起执行,之后再汇总 例子:泡方便面,电水壶烧水,一边撕调料倒入桶中

  4. 什么是管程?

    (1)Moniters,也称为监视器。在操作系统中叫监视器;在java中叫锁。

    (2)是一种同步机制,保证同一个时间,只有一个线程能去访问被保护数据或者代码

    (3)jvm同步基于进入和退出,使用管程对象实现的,在临界区加锁操作,这个过程通过管程对象进行管理。

    ==以上是老师的讲解,第三条根本听不懂,下面是博客园上的Java中的管程 - 被罚站的树 - 博客园 (cnblogs.com)=

    (1)所谓管程,指的是管理共享变量以及对其操作过程,让它们支持并发访问。

    (2)就是管理类的成员变量和成员方法,让这个类是线程安全的。

二. Lock接口

  1. 多线程编程步骤(上部。。也很无语。。。):

    (1)创建资源类,在资源类创建属性和操作方法。

    (2)创建多个线程,调用资源类的操作方法。

  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();
        }
    }
    
    
  3. 什么是Lock接口?

    Lock 锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允 许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对 象。Lock 提供了比 synchronized 更多的功能。

  4. 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。

  5. 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();
        }
    }
    
  6. 线程调用start()方法,是否马上创建线程?

    不是,有可能马上创建,也有可能过一会在创建

    start()方法调用了start0()方法,start0()方法有个关键字native,到这java代码就无能为力了,需要调用操作系统来完成,最终是由操作系统来决定的。看操作系统是否空闲。

三. 线程间通信及定制化通信

  1. 创建线程步骤(中部,下部):

    下部是用while代替了if这里已经替换,

    (1)第一步:创建资源类,在资源类创建属性和操作方法。

    (2)第二步:在资源类操作方法中进行以下操作

    ​ 1)判断

    ​ 2)干活

    ​ 3)通知

    (3)创建多个线程,调用资源类的操作方法。

    (4)第四步:用while代替if,防止虚假唤醒问题

  2. 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();
                }
            }
        }
    }
    

    仔细看看,这两段代码表达的是同一段意思。前者步骤更清晰,封装的很好。前者运用了那个创建线程的步骤。。。。无语

  3. 虚假唤醒问题:(我称虚假唤醒为偷渡,是不是很形象)

    (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()等待状态。不可能会往下执行,直接被另一个线程抢 走锁了。

    ​ 本想着断点调试一下,没想到我这么菜,操

  4. 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();
        }
    }
    
  5. 线程间定制化通信(源码)

    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. 读写锁及表锁和行锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SMDVdmD7-1632229126903)(C:\Users\hekaili\Desktop\JUC\JUC\尚硅谷宋红康2021JUC\笔记\分析图\08-读写锁.png)]
(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. 自定义线程池

在真实的开发中,我们都是使用自定义的线程池。为什么呢?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cIo01TtU-1632229126910)(C:\Users\hekaili\Desktop\JUC\JUC\尚硅谷宋红康2021JUC\笔记\分析图\为什么要使用自定义线程池.png)]

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

好难啊这个,这里老师没有讲多。。

后两章没有理解好

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值