JUCJUCJUC

内容回顾

1、并发容器类

  • ArrayList

  • HashSet

  • HashMap:Collections工具类、ConcurrentHashMap

  • ConcurrentHashMap底层

2、JUC常见工具类

  • 减少计算

  • 循环栅栏

  • 信号量

3、Callable接口

  • Callable接口如何创建线程:FutureTask

  • FutureTask特点

  • callable接口与runnable接口的区别

4、阻塞队列

5、线程池

  • 创建线程池方式:工具类和ThreadPoolExecutor

  • ThreadPoolExecutor 七个参数

  • 核心线程数设置规则

今天内容

1、线程池(重要)

第一,线程池优点

第二,线程池创建方式

第三,ThreadPoolExecutor七个参数

第四,线程池执行流程

第五,五种拒绝策略

第六,为什么不使用Executors工具类创建线程池

线程池底层工作流程(背)

问题:线程池工作原理:

第一个层面:底层使用对象ThreadPoolExecutor,这个对象里面传递七个参数,把七个参数说一遍

第二个层面:工作流程

拒绝策略

  • 线程池支持五种拒绝策略
  1. AbortPolicy(默认):直接抛出异常 ( ThreadPoolExecutor自带)

  2. CallerRunsPolicy:回到调用者,"调用者运行"一种调节机制。 ( ThreadPoolExecutor自带)

  3. DiscardOldestPolicy:抛弃队列中等待最久的任务 ( ThreadPoolExecutor自带)

  4. DiscardPolicy:默默地丢弃无法处理的任务。 如果允许任务丢失,这是最好的一种策略

    ( ThreadPoolExecutor自带)

  5. 自定义拒绝策略

复制代码
new ThreadPoolExecutor.AbortPolicy() //丢弃任务并抛出异常
new ThreadPoolExecutor.CallerRunsPolicy() //由调用线程处理该任务,谁调用谁处理
new ThreadPoolExecutor.DiscardOldestPolicy() //丢弃队列中等待最久的任务,添加新任务
new ThreadPoolExecutor.DiscardPolicy() //也是丢弃任务,但是不抛出异常。 
                    
//自定义拒绝策略
new RejectedExecutionHandler() {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        System.out.println("自定义拒绝策略");
    }
}

自定义线程池

概述

  • 创建线程池有两种方式:第一种使用工具类Executors ,第二种方式new ThreadPoolExecutor传递七个参数

  • 实际应用中都使用第二种方式ThreadPoolExecutor实现,自定义线程池

  • 使用Executors工具类创建线程池,支持线程数最大值是Integer.max_value,很容易造成大量线程堆积,导致OOM问题

如何自定义

复制代码
package com.atguigu.juc.pool;
​
import java.util.concurrent.*;
​
public class CustomizeThreadPoolDemo {
​
    public static void main(String[] args) {
        //ThreadPoolExecutor
        ExecutorService poolExecutor = new ThreadPoolExecutor(2,
                5,
                3, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
​
        try {
            for (int i = 1; i <=9; i++) {
                poolExecutor.execute(()->{
                    System.out.println(Thread.currentThread().getName()+ "执行了业务逻辑");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            poolExecutor.shutdown();
        }
    }
}

2、多线程高并发底层原理(面试)

Java内存模型

工作流程

  • jmm包含主内存和本地内存(工作内存)

  • 在主内存放共享变量

  • 每次线程都有自己的私有本地内存(工作内存)

  • 比如线程A操作共享变量,线程不能直接操作主内存里面共享变量,需要通过自己本地内存共享变量副本才能进行操作

Java内存模型的三大特性

  • 原子性

-- 不可分割,通俗我在操作时候,别人只能等待,别人不能插队的

a = 1;

a = a+1;

  • 有序性

-- 防止指令重排

  • 可见性

-- 多个线程操作同一个共享变量,多个线程之间可以看到不同线程里面本地内存修改的数据

volatile关键字

  • volatile关键字具备可见性 和 有序性,但是不具备原子性

验证可见性

复制代码
package com.atguigu.juc.jmm;
​
public class VolatileDemo {
​
    private static volatile Integer flag = 1;
​
    public static void main(String[] args)  throws InterruptedException {
​
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子线程工作内存读取到的flag的值:" + flag);
                while(flag == 1){}
                System.out.println("子线程工作内存读取到的flag的最新的值..." + flag);
            }
        }).start();
​
        Thread.sleep(500);
​
        flag = 2; //主线程修改flag的值
        System.out.println("我是主线程工作内存flag的值:" + flag);
    }
}

验证有序性

通俗,哪个线程先修改,哪个线程就首先同步主内存

如果不能保证有序性,发生指令重排

保证有序性,使用volidate关键字实现

复制代码
static volatile int a,b;

验证原子性

  • volidate不具备原子性

  • 如果保证原子性,可以加锁实现

复制代码
class Data {
    private volatile Integer number = 0;
​
    // +1
    public synchronized Integer incr() {
        return ++number;
    }
}
public class Demo {
​
    public static void main(String[] args) {
        Data dataOne = new Data();
​
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                System.out.println(dataOne.incr());
            }).start();
        }
    }
}

CAS

引入

  • 为了保证原子性,使用加锁解决

  • 实现效果:不加锁情况下,保证原子性

-- 在juc里面有包java.util.concurrent.atomic,使用这个包提供类保证变量的原子性

复制代码
//A small toolkit of classes that support lock-free thread-safe programming on single variables.
//是小型工具包,使用这个工具包实现变量不加锁情况下实现线程安全(原子性)
复制代码
class Data {
​
    private AtomicInteger atomicInteger = new AtomicInteger(0);
​
    public Integer incr() {
        return atomicInteger.incrementAndGet();
    }
​
}
复制代码
public static void main(String[] args) {
​
    //乐观锁原理:比较和交换
    AtomicInteger i = new AtomicInteger(1);
​
    //比较当前值initialValue 1 和预期值expectedValue 1,相等,则将当前值换为新值
    System.out.println("第一次更新:" +
            i.compareAndSet(1, 200));
    System.out.println("第一次更新后i的值:" + i.get());  //200
​
    System.out.println("第二次更新:" +
            i.compareAndSet(1, 300));
    System.out.println("第二次更新后i的值:" + i.get());  // 200
​
    System.out.println("第三次更新:"
            + i.compareAndSet(200, 300));
    System.out.println("第三次更新后i的值:" + i.get());  // 300
​
}

CAS理论

  • CAS (Compare-and-Swap)比较并交换, 是一种原子操作指令,用于解决多线程并发安全问题。CAS 操作是一种乐观锁算法。

  • 乐观锁:核心是使用版本号控制

  • CAS底层使用类Unsafe,这个类final类,构造私有的

  • 在Unsafe类有CAS比较并交换方法

复制代码
// U的compareAndSetInt方法:对象、对象的属性地址偏移量、预期值、修改值
public final native boolean compareAndSetInt(Object o, long offset,int expected, int x);
//拿着传入旧值,到属性地址里面比较是否相同,如果相同的,使用新值到地址替换旧值
  • CAS有缺陷:

-- 开销大

-- 不能保证代码块原子性,只能保证变量原子性

-- 默认不能解决ABA问题,更新时候携带版本号 AtomicStampedReference

AQS

AbstractQueuedSynchronizer 抽象队列同步器简称AQS,是整个java.util.concurrent包的核心。JUC下的Lock(ReentrantLock、ReentrantReadWriteLock等)以及并发工具类(Semaphore、CountDownLatch、CyclicBarrier等)就是通过AQS实现的。

AQS底层原理:

1、AQS使用volatile关键字定义一个成员变量state,state表示锁是否已被持有

2、AQS使用CAS机制对state进行原子操作,也就是对state的值进行修改。

如果state的值为0,表示没有加锁,线程抢到资源进行加锁,加锁是将state的值设置为1,返回成功获取到锁。如果state的值为1,表示当前资源有锁,如果释放锁,把state值修改为0。

3、公平锁:有多个线程抢占资源,肯定有一个线程抢到,对当前操作加锁,其他没有抢到线程再尝试抢资源,如果多次都没有抢到,进入队列进行排队等待

4、非公平锁:有多个线程抢占资源,肯定有一个线程抢到,对当前操作加锁,其他没有抢到线程再尝试抢资源,如果多次都没有抢到,进入队列进行排队等待。但是,比如现在有一个新线程请求获取当前锁,恰巧当前持有锁线程释放了,新线程立刻获取当前锁(插队)

比如现在有一个新线程请求获取当前锁,但是当前持有锁线程一直没有释放,新线程进入队列等待

自旋锁

复制代码
	public AlbumInfo getAlbumInfo(Long id) {
        //创建锁
        Lock lock = new ReentrantLock();
        try {
            boolean tryLock = lock.tryLock(3,TimeUnit.SECONDS);
            if(tryLock) { //true 获取当前锁
                //查询数据库,返回对象
                AlbumInfo albumInfo = getData(id);
                return albumInfo;
            } else { //false 获取当前锁失败
                //////////////////////////////////
                //自旋
                return getAlbumInfo(id);
            }
        }catch (Exception e) {
			
        }finally {
            //解锁
            lock.unlock();
        }
    }

3、补充-JUC并行操作

实际应用场景

  • 把项目中串行操作 修改 并行操作,提高执行效率

  • 底层:有几个操作,创建几个线程,让这些线程一起执行,最终再进行汇总

如何实现

  • 之前学习过对象FutureTask,未来任务,这个对象有特点:异步计算(并行)

  • 实际中,使用juc并行操作工具类进行实现 - CompletableFuture

CompletableFuture做并行操作考虑以下两点:

  • 第一,结合具体业务,确定当前做几个操作

  • 第二,当前这些操作之间是否有关系

复制代码
public class Demo001 {

    public static void main(String[] args) {

        //CompletableFuture.runAsync()  void
        //CompletableFuture.supplyAsync()  有返回值
        //objectCompletableFuture.thenAcceptAsync()  在一个线程执行完成之后,执行下一个任务
        //CompletableFuture.allOf().join()

        //创建自定义线程池
        ExecutorService poolExecutor = new ThreadPoolExecutor(2,
                5,
                3, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        Map<String,String> map = new HashMap<>();

        //当前三个操作
        //1 获取用户id
        CompletableFuture<Integer> completableFuture1 =
                CompletableFuture.supplyAsync(() -> {
            map.put("1","a");
            return 1;
        },poolExecutor);

        //1 和 2 有关系
        // * 2 操作如何执行,等待1操作执行完成之后返回用户id
        // * 2 等待 1执行完成之后才能执行
        //2 根据用户id获取用户信息
        CompletableFuture<Void> completableFuture2 =
                completableFuture1.thenAcceptAsync((value) -> {
             System.out.println("value:"+value);
             map.put("2","b");
        },poolExecutor);

        //3 查询所有商品
        CompletableFuture<Void> completableFuture3 = CompletableFuture.runAsync(() -> {
            map.put("3", "c");
        },poolExecutor);

        //4 等待所有子线程执行完成之后,进行汇总
        CompletableFuture.allOf(completableFuture1,
                completableFuture2,
                completableFuture3).join();

        System.out.println(map);
    }
}
相关推荐
Swift社区1 小时前
鸿蒙 PC vs Web:谁才是未来应用形态?
前端·华为·harmonyos
问心无愧05131 小时前
ctf show web入门54
前端·笔记
吴声子夜歌1 小时前
Java——ArrayList
java·arraylist
旷世奇才李先生1 小时前
Java 内置HttpClient 深度实战与性能优化全指南
java
西贝爱学习1 小时前
pdf转TXT文本,适用于文字型PDF;扫描版PDF需要使用OCR(光学字符识别)技术来识别图中的文字
java·服务器·前端
ZC跨境爬虫1 小时前
跟着 MDN 学 HTML day_43:(DocumentFragment 接口详解)
前端·javascript·vue.js·ui·html·音视频
shizhan_cloud1 小时前
MySQL 备份与恢复
数据库·mysql
青柠代码录1 小时前
【JVM】面试题-Java中有哪些引用类型
java·jvm
思麟呀1 小时前
MySQL的内置函数
数据库·mysql