Java多线程(四)锁策略(CAS,死锁)和多线程对集合类的使用

news2025/1/22 13:08:47

锁策略(CAS,死锁)和多线程对集合类的使用

 锁策略

1.乐观锁VS悲观锁

2.轻量级锁VS重量级锁

3.自旋锁VS挂起等待锁

4.互斥锁VS读写锁

5.可重入锁vs不可重入锁

死锁的第一种情况

死锁的第二种情况

死锁的第三种情况

CAS

1.实现原子类

2.实现自旋锁

偏向锁:非必要,不加锁

锁消除

锁粗化

Callable 的用法

JUC(ava.util.concurrent)

原子类

信号量 Semaphore

CountDownLatch

多线程对集合类的使用

多线程环境使用 顺序表

多线程环境使用队列

多线程环境使用哈希表

其他方面的改进:

更充分的利用了CAS机制--无锁编程

优化了扩容策略


 锁策略

上面我说过,锁是为了解决线程冲突的问题。但是我也说过加锁操作会影响程序的效率。(因为阻塞),为了应对这个我们应该合理去进行加锁操作,那么就应该有策略的操作。

1.乐观锁VS悲观锁

乐观锁:   预测接下来冲突概率不大(做的工作少)--->效率会快一些

悲观锁:预测接下了的冲突概率不大(做的多)--->x效率会慢一些

其实这两个就是预测接下来的锁冲突(阻塞等待)的概率是大,还是不大,根据这个冲突的概率,决定接下来怎么做。

Synchronized 初始使用乐观锁策略. 当发现锁竞争比较频繁的时候, 就会自动切换成悲观锁策略.

2.轻量级锁VS重量级锁

轻量级锁:加锁解锁的过程更快更高效。(一个乐观锁很可能是一个轻量级锁)

重量级锁:加锁解锁,过程更慢,更低效。(一个悲观锁很可能是一个重量级锁)

3.自旋锁VS挂起等待锁

自旋锁:是轻量级锁的一种典型实现(纯用户态的不需要经过内核态(时间相对更短))

加锁失败后,不停等待的去问是否可以加锁了

挂起等待锁:是重量级锁的一种典型实现(通过内核机制来实现挂起等待(时间更长了))

加锁失败后,先去做其他事情,等这个锁给我信号后我就回来加锁。

Synchronized 既是悲观锁,也是乐观锁,既是轻量级锁,也是重量级锁;轻量级锁部分基于自旋锁实现,重量级锁部分基于挂起等待锁实现。

Synchronized 会根据当前锁竞争的激烈程度,自适应;

  • 如果冲突不激烈,以轻量级锁或者乐观锁的状态运行
  • 如果激烈,以重量级锁或悲观锁的状态运行。

4.互斥锁VS读写锁

互斥锁:

synchronized是一个互斥锁,就单纯的加锁。通常只有两种操作:

  1. 进入代码块,加锁
  2. 出代码块,解锁

读写锁:

有一种锁,把读操作和写操作分开加锁(线程安全):

  1. 给读加锁
  2. 给写加锁
  3. 解锁

约定:

  1. 读锁和读锁之间,不会锁竞争,不会产生冲突(不会影响程序之间的效率)
  2. 写锁和写锁之间,有锁竞争(减慢速度,保证准确性)
  3. 读锁和写锁之间,有锁竞争(减慢速度,保证准确性)

Java中专门提供了读锁一个类,写锁一个类。

5.可重入锁vs不可重入锁

  • 如果一个锁,在一个线程中,连续对锁,锁了两次,不产生死锁,叫可重入锁。
  • 如果一个锁,在一个线程中,连续对锁,锁了两次,产生死锁,叫不可重入锁。

死锁的第一种情况

如何产生死锁,我们对一个代码加两次锁,此时内部的锁要等待外部的锁释放才能加锁,而此时外部的锁释放,需要等待内部锁加锁成功。然后逻辑上矛盾了,于是产生了死锁。

死锁的第二种情况

两个线程两把锁,即使单个线程是可重入锁,也会死锁。

 线程1的外部锁加锁,需要等待线程2内部锁释放,同理线程2外部锁加锁,需要等待线程1内部锁释放,此时逻辑矛盾,产生死锁。

死锁的第三种情况

哲学家,就餐问题(N个线程,M把锁)

一个桌子上有五只筷子。也有五个人,桌上有一碗面,每个人只能用一双筷子吃一口。诺是五个同时拿起一只筷子,场上就构不成一双筷子的条件,也就是谁都吃不了面。此时就死锁了。

 怎么办,很简单,五个人约定一个规则,谁先吃,谁后吃,此时就可以避开死锁的情况。

死锁的四个必要条件

  • 互斥使用:一个线程拿到一把锁后,另一个线程不能使用(根本问题锁的基本特点)
  • 不可抢占:一个线程拿到锁,只能自己主动释放,不能是被其他线程强行占有
  • 请求和保持:一个线程拿到一个锁,不去做事,反而想拿到第二把锁。
  • 循环等待:逻辑冲突。谁都拿不到。

实践中如何避免死锁?

对锁进行编号,如果需要获取多把锁,就约定加锁顺序,务必先对编号小的加锁,在对编号大的加锁。

公平锁VS非公平锁

约定:

遵循先来后到,就是公平锁,不遵守先来后到的(等概率竞争是不公平的),非公平锁。

synchronized是非公平的,要实现公平就需要在synchronized的基础上,加个队列来记录这些加锁线程的顺序。

总结一下synchronized的特点:

  1. 既是乐观锁,也是悲观锁
  2. 既是轻量级锁,也是重量级锁
  3. 轻量级锁基于自旋锁实现,重量级锁基于挂起等待实现
  4. 不是读写锁
  5. 是可重入锁
  6. 是非公平锁

CAS

CAS: 全称Compare and swap,字面意思:“比较并交换”,一个 CAS 涉及到以下操作:

我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。

1. 比较 A 与 V 是否相等。(比较)

2. 如果比较相等,将 B 写入 V。(交换)

3. 返回操作是否成功。

真实的 CAS (即cpu的一条指令)是一个原子的硬件指令完成的(具有原子性),相当于我们不加锁,就能保证线程安全。

当多个线程同时对某个资源进行CAS操作,只能有一个线程操作成功,但是并不会阻塞其他线程,其他线程只会收到操作失败的信号。

CAS 可以视为是一种乐观锁. (或者可以理解成 CAS 是乐观锁的一种实现方式)

讲到锁操作的时候,我们说过因为一个读一个写的两个线程,他们不会自己去检查变量是否发生过改变。但是CAS却可以进行自检,并返回是否成功。

基于CAS实现的操作:

1.实现原子类

标准库里提供AtomInteger类保证程序的原子性

  • AtomicBoolean
  • AtomicInteger
  • AtomicIntegerArray
  • AtomicLong
  • AtomicReference
  • AtomicStampedReference

2.实现自旋锁

通过CAS的自检性,反复检查当前的锁状态,看是否解开了;

但是CAS不是没有问题,最典型的问题A->B->A问题,其实就是我们要内存改变的值与内存的值一样,是得不断在A--B--A中不断横跳。在具体一点就是,两个线程(t1,t2)对数据进行减法,(t3)还有一个对数据进行加法,而加的数据与减的数据一样。

那么就会有一个问题。两个线程中其中一个线程(t1)提前做了减操作,接下来是(t3)加操作,此时内存的值没变,t2线程发现值是原来的值,又做了一次减操作。(这显然不是我们所期望的)

如何解决呢?

加入一个衡量内存的值是否变化的量,俗称版本号,版本号只能增加无法减少,每一次修改版本+1,这样我只需对比版本号本身就可以避免aba问题。

synchronized的锁策略:锁升级

偏向锁:非必要,不加锁

先让线程针对锁,有个标记,如果整个代码执行过程中没有遇到别的线程和我竞争这个所,我就加锁了。但是如果有人来竞争,就升级为真的锁。这样既保证了效率,也保证了线程安全。

锁消除

基础逻辑是,非必要不加锁。编译器+JVM 判断锁是否可消除,如果可以,就直接消除。检测当前代码是否多线程执行,判断是否有必要加锁,如果没有必要,但是又加上了锁,就会在编译过程中自动取消掉。

比如StringBuffer,在源码内加入了synchronized关键字。诺是单线程就必要加锁了,也就可以取消掉。

锁粗化

锁的粒度,synchronized代码块,包含代码的多少(代码越多,粒度越粗,越少,粒度越细),多数情况希望锁的粒度更小。(串行代码少,意味着并发代码就多。)

如果有一个场景需要频繁的加锁解锁,此时就会将整个场景锁起来,变成一个更粗的锁

Callable 的用法

Callable 是一个 interface . 相当于把线程封装了一个 "返回值". 方便程序猿借助多线程的方式计算结果,非常类似于Runnable,只不过返回值不是void,而是泛型

创建线程计算 1 + 2 + 3 + ... + 1000(非callable)

//创建一个类 Result , 包含一个 sum 表示最终结果, lock 表示线程同步使用的锁对象
static class Result {
    public int sum = 0;
    public Object lock = new Object();
}
public static void main(String[] args) throws InterruptedException {

    Result result = new Result();
    Thread t = new Thread() {
        @Override
        public void run() {
//main 方法中先创建 Result 实例, 然后创建一个线程 t. 在线程内部计算 1 + 2 + 3 + ... + 1000
            int sum = 0;
            for (int i = 1; i <= 1000; i++) {
                sum += i;
           }
            synchronized (result.lock) {
                result.sum = sum;
//主线程同时使用 wait 等待线程 t 计算结束
                result.lock.notify();
           }
       }
   };
    t.start();
    synchronized (result.lock) {
        //
        while (result.sum == 0) {
            result.lock.wait();
       }
//当线程 t 计算完毕后, 通过 notify 唤醒主线程, 主线程再打印结果.
        System.out.println(result.sum);
   }
}

创建线程计算 1 + 2 + 3 + ... + 1000(callable)

Callable<Integer> callable = new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 1000; i++) {
            sum += i;
       }
        return sum;
   }
};
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread t = new Thread(futureTask);
t.start();
int result = futureTask.get();
System.out.println(result);
  1. 创建一个匿名内部类, 实现 Callable 接口. Callable 带有泛型参数. 泛型参数表示返回值的类型.
  2. 重写 Callable 的 call 方法, 完成累加的过程. 直接通过返回值返回计算结果.
  3. 把 callable 实例使用 FutureTask 包装一下.
  4. 创建线程, 线程的构造方法传入 FutureTask . 此时新线程就会执行 FutureTask 内部的 Callable 的
  5. call 方法, 完成计算. 计算结果就放到了 FutureTask 对象中.
  6. 在主线程中调用 futureTask.get() 能够阻塞等待新线程计算完毕. 并获取到 FutureTask 中的结 果.

Callable中泛型是什么,就返回什么。

Callable 和 Runnable的区别

  1. Callable 和 Runnable 相对, 都是描述一个 "任务",Callable 描述的是带有返回值的任务,Runnable 描述的是不带返回值的任务。
  2. Callable 通常需要搭配 FutureTask 来使用.,FutureTask 用来保存 Callable 的返回结果. 因为Callable 往往是在另一个线程中执行的, 啥时候执行完并不确定。
  3. FutureTask 就可以负责这个等待结果出来的工作。

FutureTask 的理解,其实可以理解为,炖汤,通常炖汤我们将食物放入砂锅中,只需要等待时间过去2-3小时,砂锅就能为我们呈现一锅鲜美的汤。

JUC(ava.util.concurrent

ReentrantLock:可重入互斥锁. 和 synchronized 定位类似, 都是用来实现互斥效果, 保证线程安全

用法:

  1. lock(): 加锁, 如果获取不到锁就死等.
  2. trylock(超时时间): 加锁, 如果获取不到锁, 等待一定的时间之后就放弃加锁.(加锁失败,不会阻塞,直接返回false,更灵活)
  3. unlock(): 解锁

ReentrantLock 和 Synchronized 的区别:

  1. synchronized 是一个关键字, 是 JVM 内部实现的(大概率是基于 C++ 实现). ReentrantLock 是标准 库的一个类, 在 JVM 外实现的(基于 Java 实现).
  2. synchronized 使用时不需要手动释放锁. ReentrantLock 使用时需要手动释放. 使用起来更灵活, 但是也容易遗漏 unlock.
  3. synchronized 在申请锁失败时, 会死等. ReentrantLock 可以通过 trylock 的方式等待一段时间就 放弃.
  4. synchronized 是非公平锁, ReentrantLock 默认是非公平锁,但是提供了公平和非公平两种工作模式. 可以通过构造方法传入一个 true 开启公平锁模式.
  5. 更强大的唤醒机制. synchronized 是通过 Object 的 wait / notify 实现等待-唤醒. 每次唤醒的是一 个随机等待的线程. ReentrantLock 搭配 Condition 类实现等待-唤醒, 可以更精确控制唤醒某个指定的线程.

如何选择使用哪个锁?

  • 锁竞争不激烈的时候, 使用 synchronized, 效率更高, 自动释放更方便。
  • 锁竞争激烈的时候, 使用 ReentrantLock, 搭配 trylock 更灵活控制加锁的行为, 而不是死等。
  • 如果需要使用公平锁, 使用 ReentrantLock。

原子类

原子类内部用的是 CAS 实现,所以性能要比加锁实现 i++ 高很多。原子类有以下几个

  • AtomicBoolean
  • AtomicInteger
  • AtomicIntegerArray
  • AtomicLong
  • AtomicReference
  • AtomicStampedReference

信号量 Semaphore

本质是一个计数器,描述了当前“可用资源”的个数

  • P操作,申请资源。计数器-1;
  • V操作,释放资源。计数器+1;

如果计数器为0,就阻塞等待,等待出现资源时,及继续申请等待。

  • 创建 Semaphore 示例, 初始化为 4, 表示有 4 个可用资源。
  • acquire 方法表示申请资源(P操作), release 方法表示释放资源(V操作)
  • 创建 20 个线程, 每个线程都尝试申请资源, sleep 1秒之后, 释放资源. 观察程序的执行效果。
Semaphore semaphore = new Semaphore(4);
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        try {
            System.out.println("申请资源");
            semaphore.acquire();
            System.out.println("我获取到资源了");
            Thread.sleep(1000);
            System.out.println("我释放资源了");
            semaphore.release();
       } catch (InterruptedException e) {
            e.printStackTrace();
       }
   }
};
for (int i = 0; i < 20; i++) {
    Thread t = new Thread(runnable);
    t.start();
}

CountDownLatch

同时等待 N 个任务执行结束.

  • 构造 CountDownLatch 实例, 初始化 10 表示有 10 个任务需要完成。
  • 每个任务执行完毕, 都调用 latch.countDown() . 在 CountDownLatch 内部的计数器同时自减。
  • 主线程中使用 latch.await(); (暗中计算有几个countDown被调用了)阻塞等待所有任务执行完毕. 相当于计数器为 0 了。
public class Demo {
    public static void main(String[] args) throws Exception {
        CountDownLatch latch = new CountDownLatch(10);
        Runnable r = new Runable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(Math.random() * 10000);
                    latch.countDown();
               } catch (Exception e) {
                    e.printStackTrace();
               }
           }
       };
        for (int i = 0; i < 10; i++) {
            new Thread(r).start();
       }
   // 必须等到 10 人全部回来
        latch.await();
        System.out.println("比赛结束");

   }
}

多线程对集合类的使用

常用的集合类:ArrayList,LinkedList,HashMap,PriorityQueue。。。线程是不安全的。

如果要使用怎么办?

1.可以手动对集合的修改操作加锁。(synchronized 或者 ReentrantLock)

2.使用java标准库提供的一些线程安全的版本的集合类。

多线程环境使用 顺序表

ArrayList可用,Vertor代替,但是vertor该有的方法都用synchronized,是很老的集合,实际场景并不适用。

1.Collections.synchronizedList(new ArrayList);

  • synchronizedList 是标准库提供的一个基于 synchronized 进行线程同步的 List.
  • synchronizedList 的关键操作上都带有 synchronized

2.使用 CopyOnWriteArrayList

CopyOnWrite容器即写时复制的容器。

  • 当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy, 复制出一个新的容器,然后新的容器里添加元素,
  • 添加完元素之后,再将原容器的引用指向新的容器。

这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会

添加任何元素。

所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

优点:

  • 在读多写少的场景下, 性能很高, 不需要加锁竞争.

缺点:

  1. 占用内存较多.
  2.  新写的数据不能被第一时间读取到.

多线程环境使用队列

  1.  ArrayBlockingQueue      基于数组实现的阻塞队列
  2.  LinkedBlockingQueue     基于链表实现的阻塞队列
  3.  PriorityBlockingQueue     基于堆实现的带优先级的阻塞队列
  4.  TransferQueue     最多只包含一个元素的阻塞队列

多线程使用队列:BlockingQueue  

多线程环境使用哈希表

在多线程环境下使用哈希表可以使用:

  • Hashtable

是线程安全的,给关键方法加上synchronized,颗粒度比较粗。它对整个哈市表加锁,任何的增删查操作,都会触发加锁,也就意味着会有锁竞争。其实没有必要,哈希表是有桶的,修改值是要通过key计算hash值,然后将新元素放到链表上。

两个线程对不同量进行修改,不会产生冲突,但是由于方法上加了锁也就意味着,两个线程同时使用一个方法会阻塞。(所以不建议)

  • ConcurrentHashMap

线程是安全的,ConcurrentHashMap不是只有一把锁了,每个桶也就是链表的头结点作为一把,锁,这样针对不同的链表进行操作是不会产生的所冲突。大部分的加锁操作就没有锁冲突。

其他方面的改进:

更充分的利用了CAS机制--无锁编程

有些操作,比如获取或更新某个元素个数,就可以直接使用CAS完成,不必加锁

优化了扩容策略

对于hashTable来说,如果元素太多们就会涉及扩容,诺元素很多很多,上亿个,那么将原表大部分的元素搬到新的位置上,这个操作非常不流畅。所以呢ConcurrentHashMap,在此基础上,诺put触发扩容机制,就会一次性创建更大的内存空间,然后搬运一部分,此时就相当于存在两个hash表,此时对表操作,插入是对新表插入,删除是对旧表(看元素在那个表上)删除,查找是新旧表都查。(每一次操作。都会从旧表搬运一部分到新表)

Hashtable和HashMap、ConcurrentHashMap 之间的区别

  1. HashMap: 线程不安全. key 允许为 null
  2. Hashtable: 线程安全. 使用 synchronized 锁 Hashtable 对象, 效率较低. key 不允许为 null.
  3. ConcurrentHashMap: 线程安全. 使用 synchronized 锁每个链表头结点, 锁冲突概率低, 充分利用CAS 机制. 优化了扩容方式. key 不允许为 null

Java多线程是如何实现数据共享的?

  1. JVM 把内存分成了这几个区域:
  2. 方法区, 堆区, 栈区, 程序计数器.
  3. 其中堆区这个内存区域是多个线程之间共享的.
  4. 只要把某个数据放到堆内存中, 就可以让多个线程都能访问到。

Java创建线程池的接口是什么?参数 LinkedBlockingQueue 的作用是什么?

创建线程池主要有两种方式:

  • 通过 Executors 工厂类创建. 创建方式比较简单, 但是定制能力有限.
  • 通过 ThreadPoolExecutor 创建. 创建方式比较复杂, 但是定制能力强.

LinkedBlockingQueue 表示线程池的任务队列。 用户通过 submit / execute 向这个任务队列中添

加任务, 再由线程池中的工作线程来执行任务。

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

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

相关文章

苹果发布会:iPhone15系列

苹果将在北京时间9月13日凌晨1点召开发布会&#xff0c;本次发布会的主角是iPhone 15系列&#xff0c;包含四款机型&#xff1a;iPhone 15、iPhone 15 Plus、iPhone 15 Pro 以及 iPhone 15 Pro Max&#xff0c;本次发布会快科技全程视频直播&#xff0c;有关产品的细节也会在新…

四川百幕晟科技:抖音新店怎么快速起店?

抖音作为全球最大的短视频平台&#xff0c;拥有庞大的用户基础和强大的影响力&#xff0c;成为众多商家宣传产品、增加销量的理想选择。那么&#xff0c;如何快速开店并成功运营呢&#xff1f;下面描述了一些关键步骤。 1、如何快速开新店&#xff1f; 1、确定产品定位&#x…

系列一、前言

本系列文章是参考B站尚硅谷老师讲的 "尚硅谷Nginx教程由浅入深&#xff08;一套打通丨初学者也可掌握&#xff09;"系列课程&#xff0c;然后结合自己真实的操作而总结的系列文章。我也把自己学习、实操过程中的详细笔记以脑图的形式分享了出去&#xff0c;发现大家对…

SpringMVC实战crud增删改查

一.公共页面的跳转 1.编写页面跳转控制类 package com.YU.web;import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping;/*** author YU* create …

Apache Linki 1.3.1+DataSphereStudio+正常启动+微服务+端口号

我使用的是一键部署容器化版本&#xff0c;官方文章 默认会启动6个 Linkis 微服务&#xff0c;其中下图linkis-cg-engineconn服务为运行任务才会启动,一共七个 LINKIS-CG-ENGINECONN:38681 LINKIS-CG-ENGINECONNMANAGER:9102 引擎管理服务 LINKIS-CG-ENTRANCE:9104 计算治理入…

Linux 中的 chpasswd 命令及示例

chpasswd命令用于更改密码,尽管passwd命令也可以执行相同的操作。但它一次更改一个用户的密码,因此对于多个用户,使用chpasswd 。下图显示了passwd命令的使用。使用passwd我们正在更改来宾用户的密码。首先,您必须输入当前签名用户的密码,然后更改任何其他用户的密码。必须…

文本识别 (OCR)引擎之Tesseract的使用

Tesseract OCR Tesseract概述常见OCR识别平台下载安装配置命令使用语法测试验证 Tesseract的使用安装python库基本使用可能的异常更换语言字体库识别 Tesseract的训练 Tesseract 概述 Tesseract是一个开源文本识别 (OCR)引擎&#xff0c;是目前公认最优秀、最精确的开源OCR系统…

微服务井喷时代,我们如何规模化运维?

随着云原生技术发展及相关技术被越来越多运用到公司生产实践当中&#xff0c;有两种不可逆转的趋势&#xff1a; 1、微服务数量越来越多。原来巨型单体服务不断被拆解成一个个微服务&#xff0c;在方便功能复用及高效迭代的同时&#xff0c;也给运维带来了不少挑战&#xff1a;…

WorkPlus AI助理,基于ChatGPT的企业级知识问答机器人

随着人工智能技术的发展&#xff0c;WorkPlus AI助理以ChatGPT对话能力为基础&#xff0c;将企业数据与人工智能相结合&#xff0c;推出了面向企业的知识问答机器人。这一创新性的解决方案帮助企业高效管理和利用自身的知识资产&#xff0c;助力企业级人工智能的构建。与传统的…

React 入门实例教程

目录 一、HTML 模板 二、ReactDOM.render() 三、JSX 语法 四、组件 五、this.props.children 六、PropTypes 七、获取真实的DOM节点 八、this.state 九、表单 十、组件的生命周期 constructor() componentWillMount() render() componentDidMount() 组件生命周期…

MOV导出序列帧并在Unity中播放

MOV导出序列帧并在Unity中播放 前言项目将MOV变成序列帧使用TexturePacker打成一个图集将Json格式精灵表转换为tpsheet格式精灵表导入Unity并播放总结 鸣谢 前言 收集到一批还不错的MG动画&#xff0c;想要在Unity中当特效播放出来&#xff0c;那首先就得把MOV变成序列帧&…

Say0l的安全开发-弱口令扫描工具-My-crack【红队工具】

写在前面 终于终于&#xff0c;安全开发也练习一年半了&#xff0c;有时间完善一下项目&#xff0c;写写中间踩过的坑。 安全开发的系列全部都会上传至github&#xff0c;欢迎使用和star。 工具链接地址 https://github.com/SAY0l/my-crack 预览 My-Crack 工具介绍 更适合…

Java 毕业设计-基于SpringBoot的在线文档管理系统

基于SpringBoot的在线文档管理系统 博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W,Csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 技术栈简介 文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;sp…

2020年09月 C/C++(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

C/C++编程(1~8级)全部真题・点这里 第1题:铺砖 对于一个2行N列的走道。现在用12,22的砖去铺满。问有多少种不同的方式。 时间限制:3000 内存限制:131072 输入 整个测试有多组数据,请做到文件底结束。每行给出一个数字N,0 <= n <= 250 输出 如题 样例输入 2 8 12 1…

声网agora创建账号

1. 注册声网账号 https://sso2.agora.io/cn/v4/signup/with-sms 2. 创建项目 项目管理 - 创建项目 3. 项目配置 appid: xxxx token: xxxx 4. 开发文档 https://docportal.shengwang.cn/cn/All/code-samples?platformiOS

免费绕过苹果MDM配置锁/密码界面工具-Hackt1vator Unlock

Hackt1vator Unlock Hackt1vator Unlock是一款免费免费绕过苹果MDM配置锁/密码界面工具&#xff0c;帮助那些忘记iCloud账号密码而无法使用的iPhone、iPad的用户绕过 icloud 激活、mdm 和密码锁定&#xff0c;目前工具支持跳过MDM配置锁&#xff0c;支持绕过物主与锁定界面&am…

如何按文件名称自动归类,助您轻松管理文件

在日常工作和生活中&#xff0c;我们经常会遇到大量的文件需要管理。如果这些文件没有得到良好的归类和整理&#xff0c;不仅会浪费我们的时间和精力&#xff0c;还会给我们带来困扰和混乱。今天我们一起来看看怎么按文件名称来自动归类&#xff0c;批量整理文件&#xff0c;让…

postgresql|数据库|数据迁移神器ora2pg的安装部署和初步使用

前言&#xff1a; 有的时候有需求需要迁移Oracle数据库的数据到postgresql&#xff0c;那么&#xff0c;其实可供选择的工具是比较多的&#xff0c;但从迁移效率&#xff0c;准确度这些角度来选择的话&#xff0c;无疑还是GitHub上的开源免费工具ora2pg比较合适的。 ora2pg的…

windows系统下使用crashpad为vs2019项目在崩溃时生成dump文件(步骤超详细)

我们在刚开始项目开发时&#xff0c;经常会因为各种粗心造成各种各样的容易使项目运行时崩溃的代码&#xff0c;比如&#xff0c;给空指针的指向赋值。然而&#xff0c;当项目的文件数非常多时&#xff0c;想找到出错的代码的位置是费事而让人心烦的。crashpad就可以在项目运行…

【Linux】使用 Alist 实现阿里云盘4K播放

一、安装 Alist 官方文档 默认安装在 /opt/alist 中 curl -fsSL "https://alist.nn.ci/v3.sh" | bash -s install自定义安装路径&#xff0c;将安装路径作为第二个参数添加&#xff0c;必须是绝对路径&#xff0c;如果路径以 alist 结尾&#xff0c;则直接安装到给定…