Java无锁并发

news2024/10/7 6:43:53

共享资源

1. 不安全场景

package com.nike.erick.d05;

import lombok.Getter;

import java.util.concurrent.TimeUnit;

public class Demo01 {
    public static void main(String[] args) throws InterruptedException {
        BankService bankService = new BankService();
        for (int i = 0; i < 15; i++) {
            new Thread(() -> bankService.drawMoney()).start();
        }

        TimeUnit.SECONDS.sleep(3);
        System.out.println(bankService.getTotalMoney());
    }
}

class BankService {
    @Getter
    private int totalMoney = 10;

    public void drawMoney() {
        if (totalMoney > 0) {
            try {
                /*模仿业务事件*/
                TimeUnit.MILLISECONDS.sleep(100);
                totalMoney--;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("余额不足");
            return;
        }
    }
}

2. 解决方案

2.1 悲观锁-synchronized

  • 通过加锁,来实现线程间的互斥,实现线程安全的目的

2.2 乐观锁-CAS

  • 不加锁实现共享资源的保护
  • Compare And Set
  • JDK提供了对应的CAS类来实现不加锁
AtomicInteger

private volatile int value;

# 1. 获取最新值
public final int get();

# 2. 比较,交换
public final boolean compareAndSet(int expectedValue, int newValue)
package com.nike.erick.d05;

import lombok.Getter;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class Demo02 {
    public static void main(String[] args) throws InterruptedException {
        Bank bank = new Bank();

        for (int i = 0; i < 1000; i++) {
            new Thread(() -> bank.withDrawMoney()).start();
        }

        TimeUnit.SECONDS.sleep(5);
        System.out.println(bank.getTotalAmount().get());
    }
}

class Bank {
    /*JDK提供的cas类*/
    @Getter
    private AtomicInteger totalAmount = new AtomicInteger(100);

    public void withDrawMoney() {

        while (true) {
            /*最新值*/
            int before = totalAmount.get();
            if (before<=0){
                break;
            }

            /*想修改的值*/
            int next = before - 1;
            boolean isChanged = totalAmount.compareAndSet(before, next);

            if (isChanged) {
                break;
            }
        }
    }
}

3. CAS原理

- cas 必须和 volatile结合使用
- get()方法获取到的是类的value,被volatile修饰,
  其他线程修改该变量后,会立刻同步到主存中,方便其他线程的cas操作
- compareAndSet内部,是通过系统的 lock cmpxchg(x86架构)实现的,也是一种锁

image-20221009202916782

4. CAS效率

  • 无锁时,即使重试失败,线程一直高速运行。synchronized会让线程在没有锁的时候,发生上下文切换,进入阻塞,影响性能
  • 无锁时,线程一直在运行,如果cpu不够多且当前时间片用完,虽然不会进入阻塞,但依然会发生上下文切换,从而进入可运行状态
  • 无锁实现: 最好是线程数少于cpu的核心数目
CAS:
1. 无锁并发,无阻塞并发
2. 如果竞争激烈,重试机制必然频发触发,反而性能会收到影响
3. 基于乐观锁的思想

原子JDK

1.原子整数

  • 能够保证修改数据的结果,是线程安全的包装类型
# 功能类似
java.util.concurrent.atomic.AtomicInteger
java.util.concurrent.atomic.AtomicLong
java.util.concurrent.atomic.AtomicBoolean

1.1 常用方法

  • AtomicInteger的下面方法,都是原子性的,利用了CAS思想,简化代码
package com.erick.multithread.d4;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntUnaryOperator;

public class Demo06 {
}

class Account {

    // 无参构造为0
    private AtomicInteger account = new AtomicInteger(3);

    public void method01() {
        /*自增并返回最新结果*/
        int result = account.incrementAndGet();
        System.out.println(result);
    }

    public void method02() {
        /*获取最新结果并自增*/
        int result = account.getAndDecrement();
        System.out.println(result);
    }

    public void method03() {
        /*自减并返回最新结果*/
        int result = account.decrementAndGet();
        System.out.println(result);
    }

    public void method04() {
        /*返回最新结果并自减*/
        int result = account.getAndDecrement();
        System.out.println(result);
    }

    public void method05() {
        /*先处理并返回结果: 函数接口: IntUnaryOperator*/
        int result = account.updateAndGet(new IntUnaryOperator() {
            @Override
            public int applyAsInt(int operand) {
                return operand * 10;
            }
        });

        System.out.println(result);
    }

    public void method06() {
        /*返回结果并更新*/
        int result = account.getAndUpdate(new IntUnaryOperator() {
            @Override
            public int applyAsInt(int operand) {
                return operand * 10;
            }
        });
        System.out.println(result);
    }

    public void method07() {
        /*如果需要减,则传递负数即可*/
        int result = account.addAndGet(5);
        System.out.println(result);
    }

    public void method08() {
        int result = account.getAndAdd(5);
        System.out.println(result);
    }
}

1.2 CAS代码简化

package com.erick.multithread.d4;

import lombok.Getter;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class Demo07 {
    private static AccountService accountService = new AccountService();

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> accountService.withDraw()).start();
        }

        TimeUnit.SECONDS.sleep(5);
        System.out.println(accountService.getAccount().get());
    }
}

class AccountService {
    @Getter
    private AtomicInteger account = new AtomicInteger(100);

    public void withDraw() {
        if (account.get() <= 0) {
            return;
        }
        account.decrementAndGet();
    }

    public void withDraw(int num) {
        if (account.get() <= 0) {
            return;
        }
        account.addAndGet(num);
    }
}

1.3 CAS模拟

class BankService {
    @Getter
    private AtomicInteger leftMoney = new AtomicInteger(100);

    public void drawMoney() {
        sleepMills(1);
        detailWays(leftMoney, new IntUnaryOperator() {
            @Override
            public int applyAsInt(int operand) {
                return operand - 10;
            }
        });
    }

    private void detailWays(AtomicInteger value, IntUnaryOperator operator) {
        while (true) {
            int before = value.get();
            /**
             * 1. IntUnaryOperator   int applyAsInt(int operand);
             * 2. 函数接口,自定义实现
             */

            int after = operator.applyAsInt(before);
            if (leftMoney.compareAndSet(before, after)) {
                break;
            }
        }
    }

    private void sleepMills(int mills) {
        try {
            TimeUnit.MILLISECONDS.sleep(mills);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2. 原子引用

  • 一些其他计算场景,比如大数BigDecimal, 就要用到原子引用
  • 用法和上面原子整数类似

2.1 AtomicReference

package com.erick.multithread.d4;

import lombok.Getter;

import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

public class Demo08 {
    public static void main(String[] args) throws InterruptedException {
        AccountService01 accountService = new AccountService01();
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> accountService.withDraw(new BigDecimal("10"))).start();
        }

        TimeUnit.SECONDS.sleep(3);
        System.out.println(accountService.getBalance().get());
    }
}

class AccountService01 {
    @Getter
    private AtomicReference<BigDecimal> balance = new AtomicReference<>(new BigDecimal("1000"));

    public void withDraw(BigDecimal count) {
        while (true) {
            if (balance.get().compareTo(new BigDecimal("0")) <= 0) {
                break;
            }
            BigDecimal previous = balance.get();
            BigDecimal next = previous.subtract(count);
            boolean isChanged = balance.compareAndSet(previous, next);
            if (isChanged) {
                break;
            }
        }
    }
}

2.2 AtomicStampedReference

ABA场景

  • 线程-1,在CAS的过程中,线程2将原来的值从A改到B,然后又改回到A
  • 线程-1的CAS交换会成功,但是对比的值,其实已经被改过
  • 一般情况下,ABA并不会影响具体的业务
package com.erick.multithread.d4;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;


public class Demo09 {
    private static AtomicReference<String> result = new AtomicReference<>("A");

    public static void main(String[] args) throws InterruptedException {
        String previous = result.get();
        String next = "C";

        // 其他线程将该值从A->B 再从B->A
        method();
        TimeUnit.SECONDS.sleep(4);

        boolean isChanged = result.compareAndSet(previous, next);
        System.out.println("isChanged: " + isChanged + " result: " + result.get());
    }

    private static void method() throws InterruptedException {
        new Thread(() -> {
            while (true) {
                String previous = result.get();
                String next = "B";
                if (result.compareAndSet(previous, next)) {
                    System.out.println("A->B交换成功");
                    break;
                }
            }
        }).start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(() -> {
            while (true) {
                String previous = result.get();
                String next = "A";
                if (result.compareAndSet(previous, next)) {
                    System.out.println("B->A交换成功");
                    break;
                }
            }
        }).start();
    }
}

ABA解决

  • 具体的值和版本号
  • 只要其他线程动过了共享变量(通过值和版本号),就算cas失败
  • 可以通过版本号,得到该值前前后后被改动了多少次
package com.erick.multithread.d5;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;

public class Demo01 {
    private static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);

    public static void main(String[] args) throws InterruptedException {
        String previous = ref.getReference();
        int stamp = ref.getStamp();

        method();
        TimeUnit.SECONDS.sleep(4);

        String next = "C";
        boolean isChanged = ref.compareAndSet(previous, next, stamp, stamp + 1);
        System.out.println("isChanged: " + isChanged);
    }

    private static void method() throws InterruptedException {
        new Thread(() -> {
            String previous = ref.getReference();
            int stamp = ref.getStamp();

            String next = "B";
            boolean isChanged = ref.compareAndSet(previous, next, stamp, stamp + 1);
            System.out.println("A->B: " + isChanged);
        }).start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(() -> {
            String previous = ref.getReference();
            int stamp = ref.getStamp();

            String next = "A";
            boolean isChanged = ref.compareAndSet(previous, next, stamp, stamp + 1);
            System.out.println("B->A: " + isChanged);
        }).start();
    }
}

2.3 AtomicMarkableReference

  • 线程a在执行CAS操作时,其他线程反复修改数据,但是a线程只关心最终的结果是否变化了
package com.erick.multithread.d5;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicMarkableReference;

public class Demo02 {

    private static AtomicMarkableReference<String> result = new AtomicMarkableReference<>("A", true);

    public static void main(String[] args) throws InterruptedException {
        String previous = result.getReference();
        String next = "B";
        boolean isMarked = result.isMarked();

        method();
        TimeUnit.SECONDS.sleep(3);

        boolean isChanged = result.compareAndSet(previous, next, isMarked, !isMarked);
        System.out.println("isChanged: " + isChanged);
    }

    private static void method() throws InterruptedException {
        new Thread(() -> {
            String previous = result.getReference();
            String next = "B";
            boolean isChanged = result.compareAndSet(previous, next, result.isMarked(), !result.isMarked());
            System.out.println("A->B: " + isChanged);
        }).start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(() -> {
            String previous = result.getReference();
            String next = "A";
            boolean isChanged = result.compareAndSet(previous, next, result.isMarked(), !result.isMarked());
            System.out.println("B->A: " + isChanged);
        }).start();
    }
}

3. 原子数组

  • 上述原子整数和原子引用,只是针对一个对象的
  • 原子数组,可以存放上面的数组

3.1 不安全

package com.erick.multithread.d5;

import java.util.concurrent.TimeUnit;

public class Demo03 {

    private static int[] arr = new int[1];

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    arr[0]++;
                }
            }).start();
        }

        TimeUnit.SECONDS.sleep(5);
        System.out.println(arr[0]);
    }
}

3.2 线程安全

  • AtomicIntegerArray
  • AtomicLongArray
  • AtomicReferenceArray
package com.erick.multithread.d5;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicIntegerArray;

public class Demo04 {
    /*数组长度为1的一个数组*/
    private static AtomicIntegerArray array = new AtomicIntegerArray(1);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    /*参数一: 索引,  参数二:增加的值*/
                    array.addAndGet(0, 1);
                }
            }).start();
        }

        TimeUnit.SECONDS.sleep(5);
        System.out.println(array.get(0));
    }
}

4. 原子更新器

  • 用来原子更新对象中的字段,该字段必须和volatile结合使用
- AtomicReferenceFieldUpdater      # 引用类型字段
- AtomicLongFieldUpdater           # Long类型字段
- AtomicIntegerFieldUpdater        # Integer类型字段
package com.erick.multithread.d5;

import lombok.Data;

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

public class Demo05 {
    public static void main(String[] args) {
        Student student = new Student();
        AtomicReferenceFieldUpdater<Student, String> address =
                AtomicReferenceFieldUpdater.newUpdater(Student.class, String.class, "address");

        boolean isChanged = address.compareAndSet(student, null, "lucy");
        System.out.println("isChanged: " + isChanged);
        System.out.println(student.address);
    }

}

@Data
class Student {
    public volatile String address;
}

5. 原子累加器

  • JDK 8 以后提供了专门的做累加的类,用来提高性能
# 原理: 在有竞争的时候,设置多个累加单元, Thread-0 累加 Cell[0], Thread-1累加Cell[1]
#       累加结束后,将结果进行汇总,这样他们在累加时操作的不同的Cell变量,因此减少了CAS重试失败,从而提高性能
- LongAdder        ----          AtomicLong
package com.erick.multithread.d5;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;

public class Demo06 {
    private static AtomicLong first = new AtomicLong(0);
    private static LongAdder second = new LongAdder();

    public static void main(String[] args) throws InterruptedException {
        method01();
        method02();
    }

    private static void method01() throws InterruptedException {
        /*p循环主要目的是JIT编译器优化*/
        for (int p = 0; p < 4; p++) {
            long start = System.currentTimeMillis();
            for (int i = 0; i < 100; i++) {
                new Thread(() -> {
                    for (int j = 0; j < 100000; j++) {
                        first.incrementAndGet();
                    }
                }).start();
            }
            TimeUnit.SECONDS.sleep(2);
            System.out.println("AtomicLong:" + (System.currentTimeMillis() - start));
        }
    }

    private static void method02() throws InterruptedException {
        for (int p = 0; p < 4; p++) {
            long start = System.currentTimeMillis();
            for (int i = 0; i < 100; i++) {
                new Thread(() -> {
                    for (int j = 0; j < 100000; j++) {
                        second.increment();
                    }
                }).start();
            }
            TimeUnit.SECONDS.sleep(2);
            System.out.println("LongAdder:" + (System.currentTimeMillis() - start));
        }
    }
}

6. Unsafe类

  • 用于操作线程和内存的一个java类

6.1 源码获取

  • 并不是说线程不安全,只是说不建议开发人员使用
package sun.misc

public final class Unsafe {

    static {
        Reflection.registerMethodsToFilter(Unsafe.class, Set.of("getUnsafe"));
    }

    private Unsafe() {}

    private static final Unsafe theUnsafe = new Unsafe();
    private static final jdk.internal.misc.Unsafe theInternalUnsafe = jdk.internal.misc.Unsafe.getUnsafe();
  • 私有的成员方法,只能通过反射获取该类
package com.erick.multithread.d5;

import sun.misc.Unsafe;

import java.lang.reflect.Field;

public class Demo07 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);

        Unsafe unsafe = (Unsafe) theUnsafe.get(null);
        System.out.println(unsafe);
    }
}

6.2 修改属性

package com.erick.multithread.d5;

import lombok.Data;
import sun.misc.Unsafe;

import java.lang.reflect.Field;

public class Demo07 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);

        Unsafe unsafe = (Unsafe) theUnsafe.get(null);

        Teacher teacher = new Teacher();
        /*获取field的偏移量*/
        long idOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("id"));
        long nameOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("name"));

        /*执行cas操作*/
        boolean isIdChanged = unsafe.compareAndSwapInt(teacher, idOffset, 0, 1);
        boolean isNameChanged = unsafe.compareAndSwapObject(teacher, nameOffset, null, "erick");

        /*验证*/
        System.out.println(teacher);
        System.out.println(isIdChanged);
        System.out.println(isNameChanged);
    }
}

@Data
class Teacher {
    volatile int id;
    volatile String name;
}

不可变类

1. 日期类

1.1 SimpleDateFormat

线程不安全

package com.dreamer.multithread.day07;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Demo05 {
    public static void main(String[] args) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    Date parse = sdf.parse("2021-09-17");
                    System.out.println(parse); // 最终解析结果不一样
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

加锁解决

  • 性能会受到影响
package com.dreamer.multithread.day07;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Demo05 {
    public static void main(String[] args) {

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                synchronized (sdf) {
                    try {
                        Date parse = sdf.parse("2021-09-17");
                        System.out.println(parse);
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }

    }
}

1.2. DateTimeFormatter

  • JDK8之后提供了线程安全的类
package com.dreamer.multithread.day07;

import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;

public class Demo05 {
    public static void main(String[] args) {
        DateTimeFormatter dfm = DateTimeFormatter.ofPattern("yyyy-MM-dd");

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                TemporalAccessor parse = dfm.parse("2021-09-17");
                System.out.println(parse);

            }).start();
        }

    }
}

2.不可变类

  • 不可变类是线程安全的
  • 类中所有成员变量都是final修饰,保证不可变,保证只能读不能写
  • 类是final修饰,不会因为错误的继承来重写方法,导致了可变
# String类型: 不可变类
- 里面所有的Field都是final修饰的,保证了不可变,不可被修改:  private final byte[] value;
- 类被final修饰,保证了String类不会被继承
# 数组保护性拷贝
- 数组类型也是final修饰,如果通过构造传递,实际上是创建了新的数组和对应的String [保护性拷贝]

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

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

相关文章

H5 app开启web调试

前言&#xff1a; 在Android app逆向时&#xff0c;H5类型的app的加密通常在js中&#xff0c;所以就需要一种手段来查看源代码&#xff0c;查看加密过程。 0、如何确认h5 app 以狗东为例&#xff1a; 随便选择一个元素&#xff0c;可以看到是控件下的一个类 通过与H5类型的ap…

[附源码]SSM计算机毕业设计在线课程网站JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

基于java_ssm_vue鲜花在线销售商城网站-计算机毕业设计

现在&#xff0c;许多人都喜欢在节日的时候给家人或朋友送鲜花&#xff0c;但是有时候会因为工作忙而忘记或者是没有时间自己去买&#xff0c;同时也有些人觉得自己去买有些麻烦&#xff0c;所以鲜花网络销售是很有必要的。这个网站应该可以提供提前预定、送货上门等服务。首先…

Arduino开发实例-MAX30100传感器模块连接问题解决

MAX30100传感器模块连接问题解决 MAX30100 是一款集成脉搏血氧饱和度和心率监测传感器解决方案。 它结合了两个 LED、一个光电探测器、优化的光学器件和低噪声模拟信号处理,以检测脉搏血氧饱和度和心率信号。 MAX30100 采用 1.8V 和 3.3V 电源供电,可通过软件关断,待机电流…

【Java第33期】:在普通的Maven项目中存储Bean对象并获取和使用

作者&#xff1a;有只小猪飞走啦 博客地址&#xff1a;https://blog.csdn.net/m0_62262008?typeblog 内容&#xff1a;存储Bean对象&#xff0c;再在Spring中获取Bean对象&#xff0c;对其进行使用。 文章目录前言一&#xff0c;存储Bean对象1&#xff0c;创建Bean对象2&…

过控Matlab-串级控制系统的参数整定(二)

太原理工大学过程控制实验之串级控制系统的参数整定 过控Matlab-串级控制系统的参数整定实验内容1.根据动态特性参数法对简单控制系统的控制器参数整定2.根据稳定边界方法对简单控制系统的控制器参数整定利用稳定边界法&#xff0c;分别计算系统采用P、PI、PID调节规律时的PID控…

玩机搞机---关于安卓机型工厂固件 刷机 端口解密 解bl锁 写串 nv损坏 等相关常识

*******工程机和工厂固件方面的常识 可能很多玩机友友对什么是工厂固件比较陌生。那么今天的话题就围绕这个和大家讨论下。其实一般厂家的流程都是在一部机型推放市场之前&#xff0c;需要经过预研企划、研发设计、全面测试等诸多环节。在这一整个改善的全过程中&#xff0c;厂…

使用Eclipse搭建STM32嵌入式开发环境

1. Eclipse 软件和相关工具的安装 使用 Eclipse 开发 STM32 等嵌入式软件项目时&#xff0c;需要安装的软件或者工具有&#xff1a; Eclipse 软件本身&#xff0c;eclipse-inst-jre-win64.exe交叉编译工具链&#xff0c;gcc-arm-none-eabi-10.3-2021.10-win32make 构建工具&a…

linux读写锁

这里写目录标题读写锁的认识读写锁的相关函数练习读写锁的认识 &#xff08;1&#xff09;读写锁是一把锁 &#xff08;2&#xff09;读写锁的类型&#xff1a; pthread_rwlock_t lock 又分“读锁”&#xff08;对内存进行读操作&#xff09;和“写锁”&#xff08;对内存进行…

【数据结构Note5】- 树和二叉树(知识点超细大全-涵盖常见算法 排序二叉树 线索二叉树 平衡二叉树 哈夫曼树)

文章目录5.1 树和二叉树引入5.1.1 树的概念5.1.2 树的表示5.1.3 树中基本术语5.1.4 树的表示5.2 二叉树5.2.1 概念5.2.2 二叉树的性质5.2.3 特殊的二叉树5.2.4 二叉树的顺序存储5.2.5 二叉树的链式存储5.2.6 二叉树的深度优先遍历&#xff08;递归&#xff09;5.2.7 二叉树的遍…

[MQ] 死信队列介绍与场景描述

✨✨个人主页:沫洺的主页 &#x1f4da;&#x1f4da;系列专栏: &#x1f4d6; JavaWeb专栏&#x1f4d6; JavaSE专栏 &#x1f4d6; Java基础专栏&#x1f4d6;vue3专栏 &#x1f4d6;MyBatis专栏&#x1f4d6;Spring专栏&#x1f4d6;SpringMVC专栏&#x1f4d6;SpringBoot专…

Js逆向教程-11常见混淆AA和JJ

Js逆向教程-11常见混淆AA和JJ js默认 支持Unicode的。 所以支持所有的国家语种。 有没有哪些国家的和O很像但不是O,和0很像但不是0,和p很像但不是p 所以可以用这个相近的符号进行代码混淆。 var O00OO,o00oo;一、AA混淆和OO混淆 https://www.sojson.com/aaencode.html 这个…

Docker guide

前言 docker学习记录&#xff0c;内容参考 Docker Training Course for the Absolute Beginner Basic Command docker pull <Image:只是下载image&#xff0c;不会运行docker run <Image>:启动image实例&#xff0c;如果image不在docker host上&#xff0c;docker会…

【长难句分析精讲】状语从句

1. 状语从句九大类 时间状语从句&#xff1a;after / before / when / while / as / since / once / until地点状语从句&#xff1a;where原因状语从句&#xff1a;because / as / for / since让步状语从句&#xff1a;though / although / even if even though / while / as…

[Spring Cloud] Eureka Server安装

✨✨个人主页:沫洺的主页 &#x1f4da;&#x1f4da;系列专栏: &#x1f4d6; JavaWeb专栏&#x1f4d6; JavaSE专栏 &#x1f4d6; Java基础专栏&#x1f4d6;vue3专栏 &#x1f4d6;MyBatis专栏&#x1f4d6;Spring专栏&#x1f4d6;SpringMVC专栏&#x1f4d6;SpringBoot专…

ARM仿真器J-Link灯不亮的解决办法

感慨&#xff1a; 网络世界真是越来越封闭了啊&#xff0c;下载东西越来越难。 解决问题会越来越难。 解决仿真器Jlink灯不亮这个问题用了一下午..... 步骤&#xff1a; &#xff08;弯路&#xff1a;windows安装AT91-ISP&#xff0c;发现SAM_PROG v2.4加载bin后不能点击Write…

Android Camera性能分析 第23讲 录像Buffer Path实战和Trace分析

​ 本讲是Android Camera性能分析专题的第23讲&#xff0c;我们介绍录像Buffer Path实战和Trace分析&#xff0c;包括如下内容&#xff1a; Video Codec MediaRecorder.getSurface录像Buffer Path Trace分析Video Codec2 MediaRecorder.getSurface录像Buffer Path Trace分析…

java计算机毕业设计组成原理教学网站(附源码、数据库)

java计算机毕业设计组成原理教学网站&#xff08;附源码、数据库&#xff09; 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项…

手把手教你搭建K8S集群

搭建前提条件&#xff1a;VMare16pro CentOS-7-x86_64-DVD-2009.iso镜像文件&#xff0c; 远程连接工具Xshell或者MobaXterm 首先创建三台centos7的虚拟机&#xff0c;装虚拟机的话大家可以参照网上资料&#xff0c;非常容易。 使用 kubeadm 安装 Kubernetes 集群 1&#…

通关算法题之 ⌈二叉树⌋ 下

填充每个节点的下一个右侧节点指针 116、填充每个节点的下一个右侧节点指针 给定一个 完美二叉树 &#xff0c;其所有叶子节点都在同一层&#xff0c;每个父节点都有两个子节点。二叉树定义如下&#xff1a; struct Node {int val;Node *left;Node *right;Node *next; }填充…