JUC初级篇学习笔记

目录

一、JUC课程介绍

二、JUC-JUC概述和进程线程概念(1)

三、JUC-JUC概述和进程线程概念(2)

四、JUC-JUC概述和进程线程概念(3)

1、(代码)Main.java

五、JUC-Synchronized复习和案例分析

六、JUC-Synchronized实现案例

2、(代码)sync目录下:SaleTicket.java

七、JUC-Lock接口概述和实现案例

3、(代码)lock目录下:LSaleTicket.java

八、JUC-线程间通信-概述和案例分析

九、JUC-线程间通信-Synchronized实现案例

4、(代码)sync目录下:ThreadDemo1.java

十、JUC-线程间通信-虚假唤醒问题

十一、JUC-线程间通信-Lock实现案例

5、(代码)lock目录下:ThreadDemo2.java

十二、JUC-线程间定制化通信-案例分析

十三、JUC-线程间定制化通信-案例实现

6、(代码)lock目录下:ThreadDemo3.java

十四、JUC-集合线程安全-异常演示

7、(代码)lock目录下:ThreadDemo4.java

十五、JUC-集合线程安全-ArrayList线程不安全和解决方案(一)

十七、JUC-集合线程安全-HashSet和HashMap线程不安全

十八、JUC-多线程锁-Synchronized锁的八种情况

8、(代码)sync目录下:Lock_8.java

十九、JUC-多线程锁-公平锁和非公平锁

3、(代码)lock目录下:LSaleTicket.java

二十、JUC-多线程锁-可重入锁(1)

9、(代码)sync目录下:SyncLockDemo.java

二十二、JUC-多线程锁-死锁

10、(代码)DeadLock.java

二十三、JUC-Callable接口-概述

二十四、JUC-Callable接口-FutureTask概述和原理

二十五、JUC-Callable接口-创建线程

11、(代码)callable目录下:Demo1.java

二十六、JUC-辅助类(CountDownLatch)

12、(代码)juc目录下:CountDownLatchDemo.java

二十七、JUC-辅助类(CyclicBarrier)

13、(代码)juc目录下:CyclicBarrierDemo.java

二十八、JUC-辅助类(Semaphore)

14、(代码)juc目录下:SemaphoreDemo.java

二十九、JUC-读写锁-概述

三十、JUC-读写锁-案例实现

15、(代码)readwrite目录下:ReadWriteLockDemo.java

三十一、JUC-读写锁-读写锁的演变

三十二、JUC-读写锁-读写锁的降级

16、(代码)readwrite目录下:Demo1.java

三十三、JUC-阻塞队列-概述和架构

三十四、JUC-阻塞队列-分类和核心方法介绍

三十五、JUC-阻塞队列-核心方法演示

17、(代码)queue目录下:BlockingQueueDemo.java

三十六、JUC-线程池-概述和架构

三十七、JUC-线程池-使用方式和底层原理

18、(代码)pool目录下:ThreadPoolDemo1.java

三十八、JUC-线程池-七个参数介绍

三十九、JUC-线程池-工作流程和拒绝策略

四十、JUC-线程池-自定义线程池

19、(代码)pool目录下:ThreadPoolDemo2.java

注意事项(重要)

四十三、JUC-异步回调

20、(代码)completable目录下:CompletableFutureDemo.java


一、JUC课程介绍

JUC是java.util.concurrent也就是java并发编程包中的实用的工具类,这个工具类在企业开发中占据着举足轻重的地位

二、JUC-JUC概述和进程线程概念(1)

JDK中文文档:https://tool.oschina.net/apidocs/apidoc?api=jdk-zh

1、JUC简介

在Java中,线程部分是一个重点,本篇文章说的JUC也是关于线程的。JUC就是java.util.concurrent工具包的简称。这是一个处理线程的工具包,JDK1.5开始出现的。

2、进程与线程

比如打开一个软件,那在我的电脑中就会实际占用一个空间,那就表示现在就开启了一个进程。

我现在打开一个进程,一个进程中会有很多的线程,多线程在一个进程中进行

3、wait和sleep的区别

(1)sleep 是 Thread 的静态方法,wait 是 Object 的方法,任何对象实例都
能调用。
(2)sleep 不会释放锁,它也不需要占用锁。wait 会释放锁,但调用它的前提
是当前线程占有锁(即代码要在 synchronized 中)。
(3)它们都可以被 interrupted 方法中断。
4、并发和并行的区别
并发 同一时刻多个线程在访问同一个资源,多个线程对一个点
例子:春运抢票 电商秒杀...
并行 多项工作一起执行,之后再汇总
例子:泡方便面,电水壶烧水,一边撕调料倒入桶中

三、JUC-JUC概述和进程线程概念(2)

理解定义:

四、JUC-JUC概述和进程线程概念(3)

用户线程和守护线程

用户线程:平时用到的普通线程,自定义线程

守护线程:运行在后台,是一种特殊的线程,比如垃圾回收

当主线程结束后,用户线程还在运行,JVM存活

如果没有用户线程,都是守护线程,JVM结束

1、(代码)Main.java

package com.nanjing;

/**
 * 演示用户线程和守护线程
 * 用户线程:自定义线程    主线程结束了,用户线程还在运行,jvm存活
 * 守护线程:比如垃圾回收  没有用户线程了,都是守护线程,jvm结束
 *
 * @author xizheng
 * @date 2023-03-16 09:02:26
 */
public class Main {
    public static void main(String[] args) {
        //System.out.println("Hello world!");

        Thread aa = new Thread(() -> {
            //如果是true就是守护线程,如果是false就是用户线程
            System.out.println(Thread.currentThread().getName() + "::" + Thread.currentThread().isDaemon());
            while (true) {

            }
        }, "aa");
        //设置守护线程
        aa.setDaemon(true);
        aa.start();

        System.out.println(Thread.currentThread().getName() + " over");
    }
}

五、JUC-Synchronized复习和案例分析

synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:

1.修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;

2.修饰一个方法,被修饰的方法称为同步方法,起作用的范围是整个方法,作用的对象是调用这个方法的对象;

虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显示地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。

3、修饰一个静态的方法,起作用的范围是整个静态方法,作用的对象是这个类的所有对象;

4、修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用的对象是这个类的所有对象。

六、JUC-Synchronized实现案例

2、(代码)sync目录下:SaleTicket.java

package com.nanjing.sync;

/**
 * 卖票
 *
 * @author xizheng
 * @date 2023-03-16 09:41:20
 */
//第一步  创建资源类,定义属性和操作方法
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();
    }
}

七、JUC-Lock接口概述和实现案例

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

Lock与Synchronized的区别:

Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;

Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

3、(代码)lock目录下:LSaleTicket.java

我调用start()方法,我这个线程创建了吗?是不一定的,可能等一会创建,也可能马上创建。

源码调用start0()方法,进入到方法中

native关键字,java代码就无能为力了,其部分就是操作系统完成,最终也就是由操作系统决定的,至于什么时候创建由操作系统决定

package com.nanjing.lock;

import java.util.concurrent.locks.ReentrantLock;

/**
 * Lock锁实现卖票
 *
 * @author xizheng
 * @date 2023-03-16 09:36:21
 */
//第一步  创建资源类,定义属性和操作方法
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();
    }
}

八、JUC-线程间通信-概述和案例分析

小结(重点)

Lock和synchronized有以下几点不同:

1.Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

2、synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要finally块中释放锁;

3.Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

4、通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

5、Lock可以提高多个线程进行读操作的效率。

在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。

线程间通信

九、JUC-线程间通信-Synchronized实现案例

4、(代码)sync目录下:ThreadDemo1.java

十、JUC-线程间通信-虚假唤醒问题

使用while替代if条件判断,不管什么时候唤醒,while的条件语句都会执行一次,而if不会执行两次

package com.nanjing.sync;

/**
 * 线程间通信
 *
 * @author xizheng
 * @date 2023-03-16 13:45:00
 */
//第一步  创建资源类,定义属性和操作方法
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();
    }
}

十一、JUC-线程间通信-Lock实现案例

5、(代码)lock目录下:ThreadDemo2.java

package com.nanjing.lock;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 线程间通信-Lock实现案例
 *
 * @author xizheng
 * @date 2023-03-16 14:18:34
 */
//第一步  创建资源类,定义属性和操作方法
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();//+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();
    }
}

十二、JUC-线程间定制化通信-案例分析

十三、JUC-线程间定制化通信-案例实现

6、(代码)lock目录下:ThreadDemo3.java

package com.nanjing.lock;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 线程间定制化通信
 *
 * @author xizheng
 * @date 2023-03-16 14:32:21
 */
//第一步  创建资源类
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;
            //通知CC线程
            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();
    }
}

十四、JUC-集合线程安全-异常演示

7、(代码)lock目录下:ThreadDemo4.java

package com.nanjing.lock;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * 集合线程安全-异常演示
 * List集合线程不安全
 *
 * @author xizheng
 * @date 2023-03-16 14:49:56
 */
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();
        }
    }
}

十五、JUC-集合线程安全-ArrayList线程不安全和解决方案(一)

写时复制源码:

public boolean add(E e) {
        synchronized (lock) {
            Object[] es = getArray();
            int len = es.length;
            es = Arrays.copyOf(es, len + 1);
            es[len] = e;
            setArray(es);
            return true;
        }
    }

案例:

package com.nanjing.lock;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 集合线程安全-异常演示
 * List集合线程不安全
 *
 * @author xizheng
 * @date 2023-03-16 14:49:56
 */
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<>());

        List<String> list = new CopyOnWriteArrayList<>();//写时复制技术
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                //向集合添加内容
                list.add(UUID.randomUUID().toString().substring(0,8));
                //从集合获取内容,会出现并发修改异常(java.util.ConcurrentModificationException)
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

十七、JUC-集合线程安全-HashSet和HashMap线程不安全

     //演示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();
//        }

        //演示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();
        }

十八、JUC-多线程锁-Synchronized锁的八种情况

synchronized实现同步的基础:Java中的每一个对象都可以作为锁。

具体表现为以下3中形式

对于普通同步方法,锁是当前实例对象。

对于静态同步方法,锁是当前类的Class对象。

对于同步方法块,锁是Synchronized括号里配置的对象

8、(代码)sync目录下:Lock_8.java

package com.nanjing.sync;

import java.util.concurrent.TimeUnit;

/**
 * 多线程锁-Synchronized锁的八种情况
 *
 * @author xizheng
 * @date 2023-03-16 16:15:45
 */
class Phone {

    public static synchronized void sendSMS() throws Exception {
        //停留4秒
        TimeUnit.SECONDS.sleep(4);
        System.out.println("------sendSMS");
    }

    public static 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();
    }
}

十九、JUC-多线程锁-公平锁和非公平锁

3、(代码)lock目录下:LSaleTicket.java

二十、JUC-多线程锁-可重入锁(1)

9、(代码)sync目录下:SyncLockDemo.java

package com.nanjing.sync;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 可重入锁(递归锁)
 *
 * @author xizheng
 * @date 2023-03-16 17:02:46
 */
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();

        // new SyncLockDemo().add();//(验证是否是)可重入锁,java.lang.StackOverflowError
        // 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();
    }
}

二十二、JUC-多线程锁-死锁

10、(代码)DeadLock.java

package com.nanjing.sync;

import java.util.concurrent.TimeUnit;

/**
 * 演示死锁
 *
 * @author xizheng
 * @date 2023-03-16 17:15:35
 */
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();
    }
}

二十三、JUC-Callable接口-概述

1、目前我们学习了有两种创建线程的方法-一种是通过创建Thread类,另一种是通过使用Runnable创建线程。但是,Runnable缺少的一项功能是,当线程终止时(即run()完成时),我们无法使线程返回结果。为了支持此功能,Java中提供了Callable接口

2、

二十四、JUC-Callable接口-FutureTask概述和原理

Callable接口的特点如下(重点)

  1. 为了实现Runnable,需要实现不返回任何内容的run()方法,而对于Callable,需要实现在完成时返回结果的call()方法。
  2. call()方法可以引发异常,而run()则不能。
  3. 为实现Callable而必须重写call方法
  4. 不能直接替换runnable,因为Thread类的构造方法根本没有Callable

二十五、JUC-Callable接口-创建线程

11、(代码)callable目录下:Demo1.java

package com.nanjing.callable;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

/**
 * 找一个类,既和Runnable有关系,又和Callable也有关系
 * Runnable接口有实现类 FutureTask
 * FutureTask构造可以传递Callable
 *
 * Runnable接口 和 Callable接口
 * ① 是否有返回值
 * ② 是否抛出异常
 * ③ 实现方法名称不同,一个是run方法,一个是call方法
 *
 * @author xizheng
 * @date 2023-03-16 18:08:39
 */

//比较两个接口
//实现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 Exception {
        //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、考试,做会做的题目,最后看不会做的题目
         *
         * 汇总一次
         *
         */

    }
}

二十六、JUC-辅助类(CountDownLatch)

减少计数 CountDownLatch

CountDownLatch类可以设置一个计数器,然后通过countDown方法来进行减1的操作,使用await方法等待计数器不大于0,然后继续执行await方法之后的语句。

CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞

其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞)

当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行

12、(代码)juc目录下:CountDownLatchDemo.java

package com.nanjing.juc;

import java.util.concurrent.CountDownLatch;

/**
 * 演示 CountDownLatch
 *
 * @author xizheng
 * @date 2023-03-16 18:40:09
 */
public class CountDownLatchDemo {
    //6个同学陆续离开教室之后,设置初始值
    public static void main(String[] args) throws Exception {
        //创建CountDownLatch对象,设置初始值
        CountDownLatch countDownLatch = new CountDownLatch(6);

        //6个同学陆续离开教室之后
        for (int i = 1; i <= 4; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" 号同学离开了教室");

                //计数 -1
                countDownLatch.countDown();
            },String.valueOf(i)).start();
        }

        //等待
        countDownLatch.await();

        System.out.println(Thread.currentThread().getName()+" 班长锁门走人了");

    }
}

二十七、JUC-辅助类(CyclicBarrier)

循环栅栏CyclicBarrier

CyclicBarrier看英文单词可以看出大概就是循环阻塞的意思,在使用中CyclicBarrier的构造方法第一个参数时目标障碍数,每次执行CyclicBarrier一次障碍数会加一,如果达到了目标障碍数,才会执行cyclicBarrier.await()之后的语句。可以将CyclicBarrier理解为加1操作

13、(代码)juc目录下:CyclicBarrierDemo.java

package com.nanjing.juc;

import java.util.concurrent.CyclicBarrier;

/**
 * 循环栅栏 CyclicBarrier
 * 集齐7颗龙珠就可以召唤神龙
 *
 * @author xizheng
 * @date 2023-03-16 18:51:30
 */
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();
        }
    }
}

二十八、JUC-辅助类(Semaphore)

Semaphore的构造方法中传入的第一个参数是最大信号量(可以看成最大线程池),每个信号量初始化为一个最多只能分发一个许可证。使用acquire方法获得许可证,release方法释放许可

14、(代码)juc目录下:SemaphoreDemo.java

package com.nanjing.juc;

import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * 信号灯Semaphore
 * 6辆汽车,停3个车位
 *
 * @author xizheng
 * @date 2023-03-16 18:58:01
 */
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();
        }
    }
}

二十九、JUC-读写锁-概述

乐观锁和悲观锁(重点)

悲观锁:我现在有一个账户,我账户里面目前有1万块钱,那这个时候呢?用悲观锁是怎样的一个过程呢?比如现在第一个人对这个账户进行操作,他在操作的时候呢?对账户首先先上锁,他在上锁之后,那别人就不能再操作账户,别人只能是一个阻塞或者等待的状态,当我这个人操作之后,他要做一件事情,把锁释放,那别人才能继续操作。那别人在操作时候,他把账户首先也是上锁,他操作完之后,再进行解锁。每个人都进行上锁、解锁过程,这个过程就叫做悲观锁。

悲观的好处是什么呢?(效率很低

能解决并发中的各种问题,它的缺点就是不支持并发操作,你只能一个人一个人进行操作

乐观锁:它是支持并发的,比如说我现在账户里面有1万块钱,这个时候我两个人都可以得到账户中的数据进行操作,但是操作时候呢?乐观锁有一个特点,它需要在你的账户数据中加一个version版本号,而每次每个人都得到版本号,那这个时候呢?比如说我第一个人把账户减1000,第二个人减5000,最终两个人是不是有一个人要提交这个事务。比如说,第一个人把事务提交了,他在提交之后,除了改账户值之外,他还要做一件事情。版本号之前是不是v1.0,他在提交之后,把版本号改为v1.1,而他在改成v1.1之后,有什么特点呢?我另外一个人,在提交之前,先做个比较,比较当前数据版本号和数据库中的版本号是否一致,发现版本号不一致,最终他就会提交失败,这就叫乐观锁。通过版本号进行控制,谁先提交先改版本号,然后另外的人就不能提交。

行锁和表锁(重点)

三十、JUC-读写锁-案例实现

15、(代码)readwrite目录下:ReadWriteLockDemo.java

package com.nanjing.readwrite;

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;

/**
 * 读写锁-案例实现
 *
 * @author xizheng
 * @date 2023-03-16 19:14:24
 */
//资源类
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 Exception {
        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();
        }
    }
}

三十一、JUC-读写锁-读写锁的演变

三十二、JUC-读写锁-读写锁的降级

16、(代码)readwrite目录下:Demo1.java

package com.nanjing.readwrite;

import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 演示读写锁降级
 *
 * @author xizheng
 * @date 2023-03-16 22:50:27
 */
public class Demo1 {
    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("nanjing");

        //2 获取读锁
        readLock.lock();
        System.out.println("---read");

        //3 释放写锁
        writeLock.unlock();

        //4 释放读锁
        readLock.unlock();

    }
}

三十三、JUC-阻塞队列-概述和架构

队列:先进先出

栈:后进先出

BlockingQueue(阻塞队列) 简介

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

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

当队列是空的,从队列中获得元素的操作将会被阻塞

当队列是满的,从队列中添加元素的操作将会被阻塞

试图从空的队列中获得元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素

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

常用的队列主要有以下两种:

先进先出(FIFO):先插入的队列的元素也最先出队列,类似于排队的功能。从某种程度上来说这种队列也体现了一种公平性

后进先出(LIFO):后插入队列的元素最先出队列,这种队列优先处理最近发生的事件(栈)

为什么需要BlockingQueue

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

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

三十四、JUC-阻塞队列-分类和核心方法介绍

常用的阻塞队列:

ArrayBlockingQueue:基于数组的阻塞队列实现

LinkedBlockingQueue:基于链表的阻塞队列

三十五、JUC-阻塞队列-核心方法演示

17、(代码)queue目录下:BlockingQueueDemo.java

package com.nanjing.queue;

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

/**
 * 阻塞队列演示
 *
 * @author xizheng
 * @date 2023-03-16 23:00:59
 */
public class BlockingQueueDemo {
    public static void main(String[] args) throws Exception {
        //创建阻塞队列
        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",10L, TimeUnit.SECONDS));
    }
}

三十六、JUC-线程池-概述和架构

ThreadPool线程池

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

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

它的主要特点为

降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

提高响应速度:当任务到达时,任务可以不需要等待线程的创建就能立即执行。

提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

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

三十七、JUC-线程池-使用方式和底层原理

1、一池N线程:Executors.newFixedThreadPool(int)

2、一个任务一个任务执行,一池一线程:Executors.newSingleThreadExecutor()

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

18、(代码)pool目录下:ThreadPoolDemo1.java

package com.nanjing.pool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 线程池demo1
 *
 * @author xizheng
 * @date 2023-05-02 21:39:54
 */
public class ThreadPoolDemo1 {
    public static void main(String[] args) {
        //一池五线程
        ExecutorService threadPool1 = Executors.newFixedThreadPool(5);//5个窗口

        //一池一线程
        //ExecutorService threadPool2 = Executors.newSingleThreadExecutor();//一个窗口

        //一池可扩容线程
        //ExecutorService threadPool3 = Executors.newCachedThreadPool();
        //10个顾客请求
        try {
            for (int i = 1; i <= 100; i++) {
                //执行execute()方法以后,才创建了线程
                threadPool1.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" 办理业务");
                });
            }
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            //关闭
            threadPool1.shutdown();
        }
    }
}

三十八、JUC-线程池-七个参数介绍

ChatGPT翻译

corePoolSize:线程池中保留的线程数,即使它们是空闲的,除非设置了 allowCoreThreadTimeOut

maximumPoolSize:线程池允许的最大线程数。

keepAliveTime:当线程数大于核心线程数时,多余的空闲线程在终止之前等待新任务的最大时间。

unitkeepAliveTime 参数的时间单位。

workQueue:用于保存待执行任务的队列。该队列只会保存通过 execute 方法提交的 Runnable 任务。

threadFactory:线程池创建新线程时使用的工厂。

handler:当执行被阻塞因为线程边界和队列容量达到上限时使用的处理程序。

常用参数(重点)

corePoolSize                线程池的核心线程数

maximumPoolSize        能容纳的最大线程数

keepAliveTime               空闲线程存活时间

unit                                 存活的时间单位

workQueue                    存活提交但未执行任务的队列

threadFactory                 创建线程的工厂类

handler                            等待队列满后的拒绝策略

线程池中,有三个重要的参数,决定影响了拒绝策略:corePoolSize - 核心线程数,也即最小的线程数。workQueue - 阻塞队列。maximumPoolSize - 最大线程数

当提交任务数大于 corePoolSize 的时候,会优先将任务放到 workQueue 阻塞队列中。当阻塞队列饱和后,会扩充线程池中线程数,直到达到 maximumPoolSize 最大线程数配置。此时,再多余的任务,则会触发线程池的拒绝策略了。

总结起来,也就是一句话,当提交的任务数大于(workQueue.size() + maximumPoolSize),就会触发线程池的拒绝策略。

三十九、JUC-线程池-工作流程和拒绝策略

线程池底层工作原理(重要)

1、在创建了线程池后,线程池中的线程数为零

2、当调用execute()方法添加一个请求任务时,线程池会做出如下判断:

2.1 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;

2.2 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;

2.3 如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;

2.4 如果队列满了且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。

3 当一个线程完成任务时,它会从队列中取下一个任务来执行

4 当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:

4.1 如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。

4.2 所以线程池的所有任务完成后,它最终会收缩到corePoolSize 的大小。

拒绝策略

四十、JUC-线程池-自定义线程池

19、(代码)pool目录下:ThreadPoolDemo2.java

package com.nanjing.pool;

import java.util.concurrent.*;

/**
 * 线程池demo2
 * 自定义线程池创建
 *
 * @author xizheng
 * @date 2023-05-02 21:46:16
 */
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();
        }
    }
}

注意事项(重要)

1、项目中创建多线程时,使用常见的三种线程池创建方式,单一、可变、定长都有一定问题,原因是FixedThreadPool 和SingleThreadExecutor底层都是用LinkedBlockingQueue实现的,这个队列最大长度为 Integer.MAX_VALUE,容易导致OOM。所以实际生产一般自己通过 ThreadPoolExecutor的7个参数,自定义线程池

2、为什么不允许使用 Executors的方式手动创建线程池,如下图

四十三、JUC-异步回调

1、CompletableFuture简介

CompletableFuture 在Java里面被用于异步编程,异步通常意味着非阻塞,可以使得我们的任务单独运行在与主线程分离的其他线程中,并且通过回调可以在主线程中得到异步任务的执行状态,是否完成,和是否异常等信息。

CompletableFuture 实现了 Future,CompletionStage接口,实现了Future接口就可以兼容现在有线程池框架,而CompletionStage接口才是异步编程的接口抽象,里面定义多种异步方法,通过这两者集合,从而打造出强大的CompletableFuture类。

2、Future 与 CompletableFuture

Future在Java里面,通常用来表示一个异步任务的引用,比如我们将任务提交到线程池里面,然后我们会得到一个Future,在Future里面有isDone方法来判断任务是否处理结束,还有get方法可以一直阻塞直到任务结束然后获取结果,但整体来说这种方式,还是同步的,因为需要客户端不断阻塞等待或者不断轮询才能知道任务是否完成。

Future的主要缺点如下:

(1)不支持手动完成

我提交了一个任务,但是执行太慢了,我通过其他路径已经获取到了任务结果,现在没法把这个任务结果通知到正在执行的线程,所以必须主动取消或者一直等待它执行完成

(2)不支持进一步的非阻塞调用

通过Future的get方法会一直阻塞到任务完成,但是想在获取任务之后执行额外的任务,因为Future不支持回调函数,所以无法实现这个功能

(3)不支持链式调用

对于Future的执行结果,我们想继续传到下一个Future处理使用,从而形成一个链式的pipline调用,这在Future中是没法实现的。

(4)不支持多个Future合并

比如我们有10个Future并行执行,我们想在所有的Future运行完毕之后,执行某些函数,是没法通过Future实现的。

(5)不支持异常处理

Future的API没有任何的异常处理的api,所以在异步运行时,如果出了问题是不好定位的

20、(代码)completable目录下:CompletableFutureDemo.java

package com.nanjing.completable;

import java.util.concurrent.CompletableFuture;

/**
 * 异步回调和同步调用
 *
 * @author xizheng
 * @date 2023-05-04 08:29:41
 */
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();

        //异步调用 有返回值
        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();
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值