多线程与高并发——并发编程(2)

news2024/11/16 4:22:52

文章目录

  • 二、并发编程的三大特性
    • 1 原子性
      • 1.1 什么是原子性
      • 1.2 怎么保证原子性
        • 1.2.1 synchronized
        • 1.2.2 CAS
        • 1.2.3 Lock 锁
        • 1.2.4 ThreadLocal
    • 2 可见性
      • 2.1 什么是可见性
      • 2.2 解决可见性的方式
        • 2.2.1 volatile
        • 2.2.2 synchronized
        • 2.2.3 Lock
        • 2.2.4 final
    • 3 有序性
      • 3.1 什么是有序性
      • 3.2 as-if-serial
      • 3.3 happens-before
      • 3.4 volatile

二、并发编程的三大特性

1 原子性

1.1 什么是原子性

  • JMM(Java Memory Model)。不同的硬件和不同的操作系统在内存上的操作有一定的差异,Java 为了解决相同代码在不同操作系统上出现的各种问题,用 JMM 屏蔽掉各种硬件和操作系统带来的差异,让 Java 的并发编程可以做到跨平台。
  • JMM 规定所有变量都会存储在主内存中,在操作的时候,需要从主内存中复制一份到线程内存(CPU内存),在线程内部做计算,然后回写到主内存中(不一定!!)
  • 原子性的定义:原子性是指一个操作是不可分割的、不可中断的,一个线程在执行时,另一个线程不会影响到它。
  • 并发编程的原子性用代码阐述:
private static int count;
public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 100; i++) {
            increment();
        }
    });
    Thread t2 = new Thread(() -> {
        for (int i = 0; i < 100; i++) {
            increment();
        }
    });
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(count);
}
private static void increment() {
    try {
        Thread.sleep(10);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    count++;
}
  • 当前程序,多线程操作共享数据时,预期的结果与最终的结果不符。
  • 原子性:多线程操作临界资源,预期结果与最终结果一致。
  • 通过这个程序的分析,可以查看出,++ 操作一共分为三步:① 线程从主内存拿到数据并保存到 CPU 的寄存器中,② 在寄存器中进行 +1 操作,③ 最终将结果写回到主内存中。

1.2 怎么保证原子性

1.2.1 synchronized

  • 因为 ++操作可以从指令中查看到

image.png

  • 可以在方法上追加 synchronized 关键字或者采用同步代码块的形式来保证原子性。synchronized 可以避免多线程同时操作临界资源,在同一时间点,只会有一个线程正在操作临界资源。

image.png

1.2.2 CAS

什么是CAS?

  • CAS 即 Compare And Swap 也就是比较和交换,是一条 CPU 的并发原语。
  • 它在替换内存的某个位置的值时,首先查看内存中的值与预期值是否一致,如果一致,那么执行替换操作,这个操作是一个原子操作。
  • Java中基于Unsafe类提供了对 CAS 操作的方法,JVM会帮助我们将方法实现 CAS 汇编指令。但是要清楚 CAS 只是比较和交换,在获取原值的这个操作上,需要自己实现。
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 100; i++) {
            count.incrementAndGet();
        }
    });
    Thread t2 = new Thread(() -> {
        for (int i = 0; i < 100; i++) {
            count.incrementAndGet();
        }
    });
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(count);
}
  • Doug Lea 在 CAS 的基础上帮助我们实现了一些原子类,其中就包括现在看到的 AtomicInteger,还有其他很多原子类…
  • CAS的缺点:CAS 只能保证对一个变量的操作是原子性的,无法实现对多行代码实现原子性。
  • CAS的问题一ABA问题
    • 可以引入版本号的方式,来解决ABA的问题,Java中提供了一个类在CAS时,针对各个版本追加版本号的操作:AtomicStampeReference

image.png

public static void main(String[] args) throws InterruptedException {
    AtomicStampedReference<String> reference = new AtomicStampedReference<>("A", 1);

    String oldValue = reference.getReference();
    int oldVersion = reference.getStamp();

    boolean result = reference.compareAndSet(oldValue, "B", oldVersion, oldVersion + 1);
    System.out.println("修改1版本的:" + result);

    result = reference.compareAndSet("B", "C", 1, 2);
    System.out.println("修改2版本的:" + result);
}
  • CAS的问题二:自旋时间过长问题
    • 可以指定CAS一共循环多少次,如果超过这个次数,直接失败、或者挂起线程(自旋锁、自适应自旋锁)
    • 可以在CAS一次失败后,将这个操作暂存起来,后面需要获取结果时,将暂存操作全部执行,再返回最后的结果。比如:LongAdder。

1.2.3 Lock 锁

  • Lock锁是在JDK1.5由Doug Lea研发的,它的性能相比 synchronized在JDK1.5时期,性能好了很多。但是在JDK1.6对synchronized优化之后,性能相差不大,但是如果涉及到并发比较多时, 推荐使用 ReentrantLock,性能会更好。
  • 实现方式:
private static int count;
private static ReentrantLock lock = new ReentrantLock();
public static void increment() {
    lock.lock();
    try {
        count++;
        Thread.sleep(10);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
}
public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 100; i++) {
            increment();
        }
    });
    Thread t2 = new Thread(() -> {
        for (int i = 0; i < 100; i++) {
            increment();
        }
    });
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(count);
}
  • ReentrantLock 可以直接对比 synchronized,在功能上来说都是锁。但是 ReentrantLock 的功能性相比 synchronized 更加丰富。
  • ReentrantLock 底层是基于AQS实现的,有一个基于 CAS 维护的 state 变量来实现锁的操作。

1.2.4 ThreadLocal

  • Java 中的四种引用类型,分别是:强、软、弱、虚。在 Java 中最常用的就是强引用,把一个对象赋给另一个引用变量(User user = new User()),这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它始终处于可达状态,它是不能被垃圾回收机制回收的,即使该对象以后永远都不会被用到,JVM也不会回收。因此,强引用时造成Java内存泄漏的主要原因之一。
  • 其次是软引用(SoftReference),对于只有软引用的对象来说,当系统内存充足时不会被回收,当系统内存空间不足时被回收。软引用通常用在对内存敏感的程序中,作为缓存使用。
  • 然后是弱引用,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM内存是否充足,都会回收该对象占用的内存。可以解决内存泄漏问题,ThreadLocal 就是基于弱引用来解决内存泄漏问题。(针对 Entry 中的key
  • 最后是虚引用,它不能单独使用,必须和引用队列联合使用。虚引用的主要作用是跟踪对象被垃圾回收的状态,不过在开发中,我们用的更多的还是强引用。

ThreadLocal 保证原子性的方式,是不让多线程去操作临界资源,而是让每个线程去操作属于自己的数据。

  • 代码实现:
static ThreadLocal tl1 = new ThreadLocal();
static ThreadLocal tl2 = new ThreadLocal();
public static void main(String[] args) throws InterruptedException {
    tl1.set("123");
    tl2.set("456");
    Thread t1 = new Thread(() -> {
        System.out.println("t1:" + tl1.get()); // null
        System.out.println("t1:" + tl2.get()); // null
    });
    t1.start();
    System.out.println("main:" + tl1.get()); // 123
    System.out.println("main:" + tl2.get()); // 456
}

ThreadLocal实现原理

  • 每个线程Thread中都存储着一个成员变量,ThreadLocalMap;
  • ThreadLocal本身不存储数据,它更像是一个工具类,基于 ThreadLocal 去操作 ThreadLocalMap;
  • ThreadLocalMap 本身就是基于 Entry[] 实现的,因为一个线程可以绑定多个 ThreadLocal,这样一来,可能需要存储多个数据,所以采用 Entry[] 的形式实现;
  • 每个线程都有自己独立的 ThreadLocalMap,再基于 ThreadLocalMap 对象本身作为 key,对 value 进行存取;
  • ThreadLocalMap 的key是一个弱引用,弱引用的特点是,GC发生时必须被回收。这里是为了在 ThreadLocalMap 对象失去引用后,如果 key 的引用是强引用,会导致 ThreadLocal 对象无法被回收。

ThreadLocal 内存泄漏问题

  • 如果 ThreadLocal 引用丢失,key 因为是弱引用会被 GC 回收掉,如果同时线程还没有被回收,就会导致内存泄漏,内存中的 value 无法被回收,同时也无法被获取到。
  • 解决办法:只需要在使用完毕 ThreadLocal 对象之后,及时的调用 remove 方法,移除 Entry 即可。

image.png

2 可见性

2.1 什么是可见性

可见性问题是基于 CPU 位置出现的,CPU 处理速度非常快,相对CPU而言,去主内存获取数据这个事情太慢了,CPU提供了 L1、L2、L3 三级缓存,每次去主内存拿完数据后,就会存储到 CPU 的三级缓存,每次去三级缓存拿数据,效率肯定会提升。

但这就带来问题,现在CPU都是多核,每个线程的工作内存(CPU三级缓存)都是独立的, 会告知每个线程中做修改时,只改自己的工作内存,没有及时的同步到主内存中,导致数据不一致的问题。

image.png

  • 可见性问题的代码逻辑:
private static boolean flag = true;

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        while (flag) {
            // ...
        }
        System.out.println("t1线程结束");
    });
    t1.start();
    Thread.sleep(10);
    flag = false;
    System.out.println("主线程将flag改为false");
}

2.2 解决可见性的方式

2.2.1 volatile

volatile 是一个关键字,用来修饰成员变量。如果属性被 volatile 修改,相当于会告诉CPU,对当前属性的操作,不允许使用CPU缓存,必须去主内存操作。

volatile的内存语义

  • volatile属性被写:当写一个volatile变量时,JMM会将当前线程对应的 CPU 缓存及时的刷新到主内存中;
  • volatile属性被读:当读一个volatile变量时,JMM会将对应的CPU缓存中的内存设置为无效,必须去主内存中重新读取共享变量。

其实加了volatile就是告诉CPU,对当前属性的读写操作,不允许使用 CPU 缓存,加了 volatile 修饰的属性,会在转为汇编指令后,追加一个 lock 的前缀,CPU执行这个指令时,对于带有 lock 前缀的会做两个事情:

  • 将当前处理缓存行的数据写回到主内存;
  • 这个写回的数据,在其他CPU内行的缓存中,直接无效。

总结:volatile就是让 CPU 每次操作这个数据时,必须立即同步到主内存,以及从主内存读取数据。

private volatile static boolean flag = true;

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        while (flag) {
            // ...
        }
        System.out.println("t1线程结束");
    });
    t1.start();
    Thread.sleep(10);
    flag = false;
    System.out.println("主线程将flag改为false");
}

2.2.2 synchronized

synchronized也可以解决可见性问题,synchronized的内存语义:

  • 如果涉及到 synchronized 的同步代码块或者同步方法,获取锁资源之后,将内部涉及到的变量从 CPU 缓存中移除,必须去主内存中重新拿数据;而且在释放锁之后,会立即将CPU缓存中的数据同步到主内存。
private static boolean flag = true;

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        while (flag) {
            synchronized (MyTest.class) {
                // ...
            }
//                System.out.println(); // 打印方法内部也使用了synchronized
        }
        System.out.println("t1线程结束");
    });
    t1.start();
    Thread.sleep(10);
    flag = false;
    System.out.println("主线程将flag改为false");
}

2.2.3 Lock

Lock锁保证可见性的方式和 synchronized完全不同,synchronized基于它的内存语义,在获取锁和释放锁时,对CPU缓存做了一个同步到主内存的操作。

Lock锁时基于volatile实现的,Lock锁内部再进行加锁和释放锁时,会对一个由volatile修饰的state属性进行加减操作。

如果对volatile修饰的属性进行写操作,CPU会执行带有lock前缀的指令,CPU会降修改后的数据,从CPU缓存立即同步到主内存,同时也会将其他属性也立即同步到主内存中。还会降其他CPU缓存行中的这个数据设置为无效,必须重新去主内存中拉取。

private static boolean flag = true;
private static Lock lock = new ReentrantLock();

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        while (flag) {
            lock.lock();
            try {
                // ...
            } finally {
                lock.unlock();
            }
        }
        System.out.println("t1线程结束");
    });
    t1.start();
    Thread.sleep(10);
    flag = false;
    System.out.println("主线程将flag改为false");
}

2.2.4 final

final修饰的属性,在运行期间是不允许修改的,这样一来,就间接保证了可见性,所有多线程读取 final 属性,值肯定是一样的。

final并不是说每次取数据从主内存读取,也没这个必要,而且final和volatile不允许同时修饰一个变量。

final修饰的内容已经不允许再次别写了,而 volatile 是保证每次读写数据去主内存读取,并且volatile会影响一定的性能,就不需要同时修饰。

image.png

3 有序性

3.1 什么是有序性

在Java中,.java 文件中的内容会被编译,在执行前需要再次转为 CPU 可以识别的指令,CPU在执行这些指令时,为了提升执行效率,在不影响最终结果的前提下(满足一下要求),会对指令进行重排。

指令乱序执行的原因,是为了尽可能的发挥 CPU 的性能。Java 中的程序是乱序执行的,验证乱序执行效果:

static int a, b, x, y;

public static void main(String[] args) throws InterruptedException {
    for (int i = 0; i < Integer.MAX_VALUE; i++) {
        a = 0;
        b = 0;
        x = 0;
        y = 0;
        Thread t1 = new Thread(() -> {
            a = 1;
            x = b;
        });
        Thread t2 = new Thread(() -> {
            b = 1;
            y = a;
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();

        if (x == 0 && y == 0) {
            System.out.println("第" + i + "次,x = " + x + ", y = " + y);
        }
    }
}

DCL 单例模式由于指令重排序可能出现问题:线程可能会拿到没有初始化的对象,导致在使用时,由于内部属性为默认值,导致出现一些不必要的问题:

public class MyTest {
    private static volatile MyTest test;

    private MyTest() {}
  
    public static MyTest getInstance() {
        // B
        if (test == null) {
            synchronized (MyTest.class) {
                if (test == null) {
                    // A: 开辟空间、test指向地址、初始化
                    test = new MyTest();
                }
            }
        }
        return test;
    }
}

3.2 as-if-serial

as-if-serial语义:不论指令如何重排序,需要保证单线程的程序执行结果是不变的。而且如果存在依赖关系,那么也不可以做指令重排。

// 这种情况肯定不能做指令重排
int i = 0;
i++;

// 这种情况肯定不能做指令重排序
int j = 200;
j * 100;
j + 100;
// 这里即便出现了指令重排,也不可以影响最终的结果,20100

3.3 happens-before

具体规则

  1. 单线程happen-before原则:在同一个线程中,书写在前面的操作happen-before后面的操作。
  2. 锁的happen-before原则:同一个锁的unlock操作happen-before此锁的lock操作。
  3. volatile的happen-before原则: 对一个volatile变量的写操作happen-before对此变量的任意操作。
  4. happen-before的传递性原则: 如果A操作 happen-before B操作,B操作happen-before C操作,那么A操作happen-before C操作。
  5. 线程启动的happen-before原则:同一个线程的start方法happen-before此线程的其它方法。
  6. 线程中断的happen-before原则:对线程interrupt方法的调用happen-before被中断线程的检测到中断发送的代码。
  7. 线程终结的happen-before原则:线程中的所有操作都happen-before线程的终止检测。
  8. 对象创建的happen-before原则:一个对象的初始化完成先于他的finalize方法调用。

**JMM 只有不出现上述 8 种情况时,才不会触发指令重排效果。**不需要过分的关注happens-before原则,只需要可以写出线程安全的代码就可以了。

3.4 volatile

如果需要让程序对某一个属性的操作不出现指令重排,除了满足 happens-before 原则之外,还可以基于 volatile 修饰属性,从而对这个属性的操作,就不会出现指令重排问题。

volatile如何实现的禁止指令重排?

  • 内存屏障概念,可以将内存屏障看成一条指令。
  • volatile会在两个操作之间,添加一道指令,这个指令就可以避免上下执行的其它指令进行重排序。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/929557.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

iOS HealthKit 介绍

文章目录 一、简介二、权限配置1. 在开发者账号中勾选HealthKit2. 在targets的capabilities中添加HealthKit。3. infoPlist需要配置权限 三、创建健康数据管理类1. 引入头文件2. 健康数据读写权限3. 检查权限4. 读取步数数据5. 写入健康数据 四、运行获取权限页面 一、简介 He…

Linux 下 Mysql 的使用(Ubuntu20.04)

文章目录 一、安装二、使用2.1 登录2.2 数据库操作2.2.1 创建数据库2.2.2 删除数据库2.2.3 创建数据表 参考文档 一、安装 Linux 下 Mysql 的安装非常简单&#xff0c;一个命令即可&#xff1a; sudo apt install mysql-server检查安装是否成功&#xff0c;输入&#xff1a; …

【KingSCADA】问题处理:记录KS历史报警查询异常

哈喽&#xff0c;大家好&#xff01;我是雷工。 本篇记录KingSCADA的历史报警应用中的一个问题&#xff0c;及处理过程。 一、问题描述 最近客户遇到这么一个问题&#xff1a;当打开历史报警窗界面&#xff0c;自动加载的报警信息中有显示最近几天的报警信息&#xff0c;但当…

基于亚马逊云科技无服务器服务快速搭建电商平台——部署篇

受疫情影响消费者习惯发生改变&#xff0c;刺激了全球电商行业的快速发展。除了依托第三方电商平台将产品销售给消费者之外&#xff0c;企业通过品牌官网或者自有电商平台销售商品也是近几年电商领域快速发展的商业模式。独立站电商模式可以进行多方面、全渠道的互联网市场拓展…

【UniApp开发小程序】私聊功能uniapp界面实现 (买家、卖家 沟通商品信息)【后端基于若依管理系统开发】

文章目录 效果显示WebSocket连接使用全局变量WebSocket连接细节 最近和自己聊天的用户信息界面效果界面代码最近的聊天内容太长日期时间显示未读消息数量显示 私聊界面界面展示代码实现英文长串不换行问题聊天区域自动滑动到底部键盘呼出&#xff0c;聊天区域收缩&#xff0c;聊…

软考A计划-系统集成项目管理工程师-知识产权管理

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…

基于黏菌算法优化的BP神经网络(预测应用) - 附代码

基于黏菌算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码 文章目录 基于黏菌算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码1.数据介绍2.黏菌优化BP神经网络2.1 BP神经网络参数设置2.2 黏菌算法应用 4.测试结果&#xff1a;5.Matlab代码 摘要…

分布式 - 服务器Nginx:一小时入门系列之 HTTPS协议配置

文章目录 1. HTTPS 协议2. 生成 SSL 证书和私钥文件3. 配置 SSL 证书和私钥文件4. HTTPS 协议优化 1. HTTPS 协议 HTTPS 是一种通过计算机网络进行安全通信的协议。它是HTTP的安全版本&#xff0c;通过使用 SSL 或 TLS 协议来加密和保护数据传输。HTTPS的主要目的是确保在客户…

AI夏令营第三期用户新增挑战赛学习笔记

1、数据可视化 1.数据探索和理解&#xff1a;数据可视化可以帮助我们更好地理解数据集的特征、分布和关系。通过可视化数据&#xff0c;我们可以发现数据中的模式、异常值、缺失值等信息&#xff0c;从而更好地了解数据的特点和结构。2.特征工程&#xff1a;数据可视化可以帮助…

[Linux]文件IO

文章目录 1. 文件描述符1.1 虚拟地址空间1.1.1 存在的意义1.1.2 分区 1.2 文件描述符1.2.1 文件描述符1.2.2 文件描述符表 2. Linux系统文件IO2.1 open/close2.1.1 函数原型2.1.2 close函数原型2.1.3 打开已存在文件2.1.4 创建新文件2.1.5 文件状态判断 2.2 read/write2.2.1 re…

论文解读 | ScanNet:室内场景的丰富注释3D重建

原创 | 文 BFT机器人 大型的、有标记的数据集的可用性是为了利用做有监督的深度学习方法的一个关键要求。但是在RGB-D场景理解的背景下&#xff0c;可用的数据非常少,通常是当前的数据集覆盖了一小范围的场景视图&#xff0c;并且具有有限的语义注释。 为了解决这个问题&#…

C++学习记录——이십오 C++11(1)

文章目录 1、列表初始化2、声明decltype 3、STL新容器小总结 4、右值引用1、概念2、使用场景&#xff08;包含移动构造&#xff09;3、完美转发4、移动赋值5、C98的const引用延长生命周期 1、列表初始化 大括号{}来代替初始化&#xff0c;并且是所有类型。 struct ZZ {int _x…

RabbitMQ---订阅模型-Fanout

1、 订阅模型-Fanout Fanout&#xff0c;也称为广播。 流程图&#xff1a; 在广播模式下&#xff0c;消息发送流程是这样的&#xff1a; 1&#xff09; 可以有多个消费者 2&#xff09; 每个消费者有自己的queue&#xff08;队列&#xff09; 3&#xff09; 每个队列都要绑定…

记录 JSONObject.parseObject json对象转换 对象字段为null

1.业务背景 使用websocket 接收消息都是String类型&#xff0c;没办法自定义实体类接收&#xff0c;所以接发都必须将json 转 对象 对象转 json。 这是我最开始的实体类&#xff0c;也就是转换的类型 package com.trinity.system.domain;import lombok.AllArgsConstructor; im…

浏览器跨域

生活中的事跟跨域有什么关系&#xff0c;那必须有。 跨域的产生是浏览器的安全机制引起的&#xff0c;只有在使用Ajax时才会发生。简单来说就是你可以通过ajax发送请求&#xff0c;但要看远程服务器脸色&#xff0c;他没授权&#xff0c;浏览器这个老六就给拦截了&#xff0c;不…

Java之ApI之Math类详解

1 Math类 1.1 概述 tips&#xff1a;了解内容 查看API文档&#xff0c;我们可以看到API文档中关于Math类的定义如下&#xff1a; Math类所在包为java.lang包&#xff0c;因此在使用的时候不需要进行导包。并且Math类被final修饰了&#xff0c;因此该类是不能被继承的。 Math类…

6.基于二阶锥规划的主动配电网最优潮流求解

matlab代码&#xff1a; 6.基于二阶锥规划的主动配电网最优潮流求解 参考文献&#xff1a;主动配电网多源协同运行优化研究_乔珊 摘要&#xff1a;最优潮流研究在配 电网规划运行 中不可或缺 &#xff0c; 且在大量分布式能源接入 的主动配 电网环境下尤 为重要 。传统的启发…

Spring 与【MyBatis 】和【 pageHelper分页插件 】整合

目录 一、Spring整合MyBatis 1. 导入pom依赖 2. 利用mybatis逆向工程生成模型层层代码 3. 编写配置文件 4. 注解式开发 5. 编写Junit测试类 二、AOP整合pageHelper分页插件 1. 创建一个AOP切面 2. Around("execution(* *..*xxx.*xxx(..))") 表达式解析 3. 编…

Java IDEA Web 项目 1、创建

环境&#xff1a; IEDA 版本&#xff1a;2023.2 JDK&#xff1a;1.8 Tomcat&#xff1a;apache-tomcat-9.0.58 maven&#xff1a;尚未研究 自行完成 IDEA、JDK、Tomcat等安装配置。 创建项目&#xff1a; IDEA -> New Project 选择 Jakarta EE Template&#xff1a;选择…

一文了解SpringBoot中的Aop

目录 1.什么是Aop 2.相关概念 3.相关注解 4.为什么要用Aop 5.Aop使用案例 1.什么是Aop AOP&#xff1a;Aspect Oriented Programming&#xff0c;面向切面&#xff0c;是Spring三大思想之一&#xff0c;另外两个是 IOC-控制反转 DI-依赖注入 (Autowired、Qualifier、Re…