JUC——并发编程—第四部分

理解JMM

Volatile是Java虚拟机提供的轻量级的同步机制。有三大特性。

1.保证可见性

2.不保证原子性

3.禁止指令重排

定义:Java内存模型,是一个概念。

关于JMM的一些同步的约定:

1、线程解锁前,必须把共享变量立刻刷回主存.

2、线程加锁前,必须读取主存中的最新值到工作内存中!

3、加锁和解锁是同一把锁。

线程工作内存和主内存

这里面涉及到8个操作。线程A将变量flag从主存读取出来是read,加载到自己的工作内存然后执行引擎使用工作内存里的flag,用完放回工作内存,解锁前把工作内存里面的变量刷回主存。

问题:

关于主内存与工作内存之间的交互协议,即一个变量如何从主内存拷贝到工作内存。如何从工作内存同步到主内存中的实现细节。java内存模型定义了8种操作来完成。这8种操作每一种都是原子操作。8种操作如下:

  • lock(锁定):作用于主内存,它把一个变量标记为一条线程独占状态;

  • read(读取):作用于主内存,它把变量值从主内存传送到线程的工作内存中,以便随后的load动作使用;

  • load(载入):作用于工作内存,它把read操作的值放入工作内存中的变量副本中;

  • use(使用):作用于工作内存,它把工作内存中的值传递给执行引擎,每当虚拟机遇到一个需要使用这个变量的指令时候,将会执行这个动作;

  • assign(赋值):作用于工作内存,它把从执行引擎获取的值赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的指令时候,执行该操作;

  • store(存储):作用于工作内存,它把工作内存中的一个变量传送给主内存中,以备随后的write操作使用;

  • write(写入):作用于主内存,它把store传送值放到主内存中的变量中。

  • unlock(解锁):作用于主内存,它将一个处于锁定状态的变量释放出来,释放后的变量才能够被其他线程锁定;

Java内存模型还规定了执行上述8种基本操作时必须满足如下规则:

(1)不允许read和load、store和write操作之一单独出现(即不允许一个变量从主存读取了但是工作内存不接受,或者从工作内存发起会写了但是主存不接受的情况),以上两个操作必须按顺序执行,但没有保证必须连续执行,也就是说,read与load之间、store与write之间是可插入其他指令的。

(2)不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。

(3)不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中。

(4)一个新的变量只能从主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。

(5)一个变量在同一个时刻只允许一条线程对其执行lock操作,但lock操作可以被同一个条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。

(6)如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。

(7)如果一个变量实现没有被lock操作锁定,则不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定的变量。

(8)对一个变量执行unlock操作之前,必须先把此变量同步回主内存(执行store和write操作)。

演示代码

public class JMMdemo{private static int num=0;public static void main(String[] args) throws InterruptedException {new Thread(()->{while(num==0){}System.out.println("t退出了");}).start();TimeUnit.SECONDS.sleep(1);num=1;System.out.println(num);}
}

在上面代码里面,主线程更改了num的值之后成功写会主线程,但是,子线程不知道,所以这里就有问题了。需要让子线程知道主内存的值被修改。 

Volatile可见性及非原子性验证

保证可见性

这里只需要加一个Volatile关键字就可以了。 

加完后发现成功退出死循环。  

非原子性

原子性:不可分割

事务就具有原子性,要么同成功,要么同失败。

public class demo02 {//不能保证原子性的,即结果不是20000private  volatile static  int num=0;private static Lock lock=new ReentrantLock();//要么用Lock锁,要么用synchronized都可以保证。public  static void add(){
//        lock.lock();num++;
//        lock.unlock();}public static void main(String[] args) {//理论为20000,实际不是for(int i=0;i<20;i++){new Thread(()->{for(int j=0;j<1000;j++) {add();}}).start();}while(Thread.activeCount()>2){  //确保20条线程都运行完,只剩main和gcThread.yield();  //线程礼让}System.out.println(num);}
}

使用原子类解决原子性问题,消耗资源没有那两个大。 

public class demo02 {//不能保证原子性的,即结果不是20000//原子类的Integerprivate  volatile static AtomicInteger num=new AtomicInteger();private static Lock lock=new ReentrantLock();//要么用Lock锁,要么用synchronized都可以保证。public  static void add(){
//        lock.lock();
//        num++; //不是原子性操作
//        lock.unlock();num.getAndDecrement();// +1方法.底层用的CAS}public static void main(String[] args) {//理论为20000,实际不是for(int i=0;i<20;i++){new Thread(()->{for(int j=0;j<1000;j++) {add();}}).start();}while(Thread.activeCount()>2){  //确保20条线程都运行完,只剩main和gcThread.yield();  //线程礼让}System.out.println(num);}
}

 里面涉及到的一个Unsafe类是一个很特殊的存在。

指令重排详解

定义:写的源代码在变成目标代码之前会进行一个代码优化。这就涉及到重排。

在保证结果正确的前提下进行指令重排。

可能造成的影响结果。

线程A线程B
x=ay=b
b=1a=2

 一开始x,y,a,b都是0,正常结果应该是x=0,y=0.

两个线程进行指令重排之后可能会变成这样

线程A线程B
b=1a=2
x=ay=b

对于线程A来说命令顺序不重要,所以有可能会打乱。

结果变成:x=2,y=1.

加了volatile之后就会避免指令重排了。

CPU中有一个内存屏障。

作用:

1、保证特定的操作的执行顺序 !

2、可以保证某些变量的内存可见性!(利用这些特性volatile实现了可见性 )

内存屏障在单例模式使用最多。

彻底玩转单例模式

饿汉式单例

饿汉式(Eager Initialization)单例模式: 饿汉式单例模式是在类加载时就创建实例对象,无论是否需要。这意味着在程序运行时,单例实例会立即被创建。饿汉式的实现简单,但可能会浪费内存,因为即使在某些情况下没有使用单例对象,它也会被创建。

  1. 在下列代码中,instance 是在类加载时创建的,因此它是一个饿汉式单例。

/*** 饿汉式单例*/
public class Hungry {//没有使用的话可能会浪费空间。private byte[] data1=new byte[1024*1024];private byte[] data2=new byte[1024*1024];private byte[] data3=new byte[1024*1024];private byte[] data4=new byte[1024*1024];private Hungry(){}private  final static Hungry Hungry=new Hungry();public static Hungry getInstance(){return Hungry;}
}

DCL懒汉式单例

懒汉式(Lazy Initialization)单例模式: 懒汉式单例模式是在首次需要时才创建实例对象。这种方式可以避免不必要的内存消耗,但需要注意线程安全性,因为在多线程环境中,多个线程可能同时尝试创建实例。为了确保线程安全,可以使用双重检查锁定(Double-Checked Locking,DCL)来延迟初始化,如下所示:

public class LazySingleton {private static volatile LazySingleton instance;private LazySingleton() { }public static LazySingleton getInstance() {if (instance == null) {synchronized (LazySingleton.class) {if (instance == null) {instance = new LazySingleton();}}}return instance;}
}

反射破坏单例

懒汉式里面会等到用到时才创建,在多线程下会破坏单例,可以使用双重检测锁模式的懒汉式单例。但是万一有指令重排的话还会有别的问题。

/*** new的过程* 1.分配内存空间* 2.执行构造方法,初始化对象* 3.把这个对象指向该空间** 123* 132 A 线程指令重排*     B 进来后发现不为null了,但是实际还没完成构造,会直接返回一个null*/

并且这里可以用反射机制破解单例模式,成功创建出两个实例。

但是可以直接锁住class对象避免反射破坏,但是这样会有三重检测。


/*** 懒汉式单例模式*/
public class Lazyman {private Lazyman(){synchronized (Lazyman.class){if(lazyman!=null)throw new RuntimeException("不要使用反射破坏单例模式");}}private volatile static Lazyman lazyman;//双重检测锁模式的懒汉式单例,简称DCLpublic static Lazyman getInstance(){if(lazyman==null) {synchronized (Lazyman.class){if(lazyman==null) {lazyman = new Lazyman();   //非原子性操作,}}}return lazyman;}public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {Lazyman instance=Lazyman.getInstance();Lazyman instance2=Lazyman.getInstance(); //两个获得的都是同一个实例//下面利用反射无视私有的构造器Constructor<Lazyman> declaredConstructor = Lazyman.class.getDeclaredConstructor(null);declaredConstructor.setAccessible(true);  //破坏私有权限Lazyman lazyman1 = declaredConstructor.newInstance();//调用默认无参构造方法System.out.println(instance);System.out.println(instance2);System.out.println(lazyman1);}/*** new的过程* 1.分配内存空间* 2.执行构造方法,初始化对象* 3.把这个对象指向该空间** 123* 132 A 线程指令重排*     B 进来后发现不为null了,但是实际还没完成构造,会直接返回一个null*/
}

虽然但是,这里还是可以使用构造器调用私有的默认构造方法来创建两个不同的实例且不会报错,只要不调用它的getInstance方法即可。private volatile static Lazyman lazyman;就会一直为空。

解决方法:用一个新的变量

/*** 懒汉式单例模式*/
public class Lazyman {private static boolean qinjiang=false;private Lazyman(){synchronized (Lazyman.class){if(qinjiang==false) {qinjiang=true;}else{throw new RuntimeException("不要使用反射破坏单例模式");}}}private volatile static Lazyman lazyman;//双重检测锁模式的懒汉式单例,简称DCLpublic static Lazyman getInstance(){if(lazyman==null) {synchronized (Lazyman.class){if(lazyman==null) {lazyman = new Lazyman();   //非原子性操作,}}}return lazyman;}public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//        Lazyman instance=Lazyman.getInstance();
//        Lazyman instance2=Lazyman.getInstance(); //两个获得的都是同一个实例//下面利用反射无视私有的构造器Constructor<Lazyman> declaredConstructor = Lazyman.class.getDeclaredConstructor(null);declaredConstructor.setAccessible(true); //破坏私有权限Lazyman lazyman1 = declaredConstructor.newInstance();Lazyman lazyman2 = declaredConstructor.newInstance();System.out.println(lazyman1);System.out.println(lazyman2);}
}

但是还有反转,如果可以知道有一个qinjiang的变量可以通过破坏私有权限修改它的值。照样可以破坏其单例模式。这里就不给代码了。

 枚举安全

在其源码里面可以看见如果是枚举类型会说不能使用反射破坏枚举对象。

没有无参构造,只有有参构造

//Enum本身也是一个class类
public enum Enumsigle{INSTANCE;public Enumsigle getInstance(){return INSTANCE;}
}
class Test{public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//        Enumsigle enumsigle1= Enumsigle.INSTANCE;
//        Enumsigle enumsigle2= Enumsigle.INSTANCE;
//        System.out.println(enumsigle1);
//        System.out.println(enumsigle2);  //输出同一个实例Enumsigle enumsigle1= Enumsigle.INSTANCE;Constructor<Enumsigle> declaredConstructor = Enumsigle.class.getDeclaredConstructor(null);declaredConstructor.setAccessible(true);Enumsigle enumsigle2 = declaredConstructor.newInstance();System.out.println(enumsigle1);System.out.println(enumsigle2);}
}

 这里会报错没有这个空参构造方法.但是idea里面是可以看见有的

经过反编译之后可以看见也是有这个空参构造的。

使用jad生成的文件可以看见有一个有参构造

 加上参数之后可以看见正确的报错

//Enum本身也是一个class类
public enum Enumsigle{INSTANCE;public Enumsigle getInstance(){return INSTANCE;}
}
class Test{public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//        Enumsigle enumsigle1= Enumsigle.INSTANCE;
//        Enumsigle enumsigle2= Enumsigle.INSTANCE;
//        System.out.println(enumsigle1);
//        System.out.println(enumsigle2);  //输出同一个实例Enumsigle enumsigle1= Enumsigle.INSTANCE;Constructor<Enumsigle> declaredConstructor = Enumsigle.class.getDeclaredConstructor(String.class,int.class);declaredConstructor.setAccessible(true);Enumsigle enumsigle2 = declaredConstructor.newInstance();System.out.println(enumsigle1);System.out.println(enumsigle2);}
}

 雀氏知道了反射不能破坏枚举的单例模式。

深入理解CAS

CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么执行操作,否则不执行。如果不是就一直循环

CAS 是一种基于内存值的比较和条件交换的操作,它通常用于实现线程安全的数据结构和算法。CAS 操作包括三个主要步骤:

  1. 读取内存值:首先,CAS 操作会读取一个共享变量的当前值。

  2. 比较值:接下来,CAS 操作会比较读取到的值与预期的值是否相等。如果相等,表示共享变量的值没有被其他线程修改,可以继续执行下一步。如果不相等,CAS 操作将失败,不会执行后续步骤。

  3. 条件交换:如果比较成功,CAS 操作会尝试将共享变量的值修改为新的值。这一步是原子操作,意味着在这一步中,如果有其他线程尝试修改共享变量,它们会失败并且不会覆盖新值。

CAS 的主要优点是它是非阻塞的,这意味着它不会使线程陷入阻塞等待其他线程完成操作。它通过循环重试来实现,直到成功为止。这使得 CAS 成为一种高效的多线程同步机制。

在 Java 中,java.util.concurrent 包中的一些类,如 AtomicIntegerAtomicLongAtomicReference,使用 CAS 操作来实现线程安全的原子操作。此外,JVM 和 Java 编程语言的规范也使用了 CAS 来定义内存可见性和线程同步的行为,以确保多线程程序的正确性。

public class CASDemo {//CAS compareAndSet比较并交换public static void main(String[] args) {AtomicInteger atomicInteger=new AtomicInteger(2022);//底层用了CAS//期望,更新
//        public final boolean compareAndSet(int expect, int update)//如果期望的值达到了,就更新,否则不更新,CAS是CPU的并发原语!System.out.println(atomicInteger.compareAndSet(2022, 2023));System.out.println(atomicInteger.get());System.out.println(atomicInteger.compareAndSet(2022, 2023));}
}

缺点:

1.循环会耗时

2.一次只能保证一个共享变量的原子性

3.ABA问题 

unsafe方法

在AtomicInteger原子类的底层自增的方法是如下操作,会调用一个unsafe的compareAndSwapInt

 

CAS的ABA问题(狸猫换太子(乐观锁思想))

CAS(Compare-and-Swap)是一种原子操作,通常用于多线程编程中实现并发控制。CAS 操作包括读取一个共享变量的当前值,比较它与预期值,如果相等则执行更新操作,否则不做任何操作。CAS 通常用于实现无锁算法和数据结构。

问题中的 "ABA" 指的是一个特定的并发问题,它涉及到如下情况:

  1. 线程 A 读取一个共享变量的值为 A。
  2. 线程 B 修改该共享变量的值为 B。
  3. 线程 C 修改该共享变量的值再次改回 A。

从线程 A 的角度来看,它在执行 CAS 操作时读取的共享变量值仍然是 A,因此 CAS 操作成功,尽管实际上共享变量的值在此期间已经经历了改变。这就是 "ABA 问题" 的本质,即虽然共享变量的值经历了 A -> B -> A 的变化,但线程 A 并未察觉到这一点。

ABA 问题可能导致意外行为和错误,特别是在需要确保数据的一致性和正确性的情况下。为了解决 ABA 问题,通常需要在 CAS 操作中引入版本号或标记,以确保只有在预期值匹配的情况下才执行更新操作。这可以通过引入额外的字段,如版本号,来实现。

总之,ABA 问题是与 CAS 操作相关的一个并发问题,它需要特殊的处理来避免影响程序的正确性。解决方法通常包括引入版本号或标记来增加 CAS 操作的安全性。

版本号?这不就是乐观锁吗?

原子引用

例如,Java 中的 java.util.concurrent.atomic 包中的原子类,如 AtomicStampedReferenceAtomicMarkableReference,就是为了解决 ABA 问题而设计的,它们在 CAS 操作中包含了版本号或标记,以确保 CAS 操作能够正确地检测到变化。

在原子引用中的包装类问题

**Integer 使用了对象缓存机制,默认范围是-128~ 127,推荐使用静态工厂方法 valueof 获取对象实例,而不是 new因为 valueof 使用缓存,而 new 一定会创建新的对象分配新的内存空间;**

包装类有毒,包装类不存在引用,只是重新创建。

一般泛型比较的都是一个对象,对象的话就都是唯一的。 

public class CASDemo {public static void main(String[] args) {//z注意: 如果泛型是包装类,注意对象的引用问题AtomicStampedReference<Integer> integerAtomicReference = new AtomicStampedReference<>(1,1);//期望,更新new Thread(()->{int stamp=integerAtomicReference.getStamp();System.out.println("a1=>"+stamp);System.out.println(integerAtomicReference.compareAndSet(1, 2,integerAtomicReference.getStamp(), integerAtomicReference.getStamp() + 1));//版本号+1System.out.println("a2=>"+integerAtomicReference.getStamp());//再改回去System.out.println(integerAtomicReference.compareAndSet(2, 1,integerAtomicReference.getStamp(), integerAtomicReference.getStamp() + 1));//版本号+1System.out.println("a3=>"+integerAtomicReference.getStamp());},"a").start();new Thread(()->{int stamp=integerAtomicReference.getStamp();System.out.println("b1=>"+integerAtomicReference.getStamp());try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(integerAtomicReference.compareAndSet(1, 5,stamp, stamp + 1));//版本号+1System.out.println("b2=>"+integerAtomicReference.getStamp());},"b").start();}
}

执行结果如下,现在成功解决了ABA问题

 各种锁的理解

1、公平锁、非公平锁

公平锁 : 非常公平,不能够插队,必须先来后到 !

非公平锁 : 非常不公平,可以插队(Locksynchronized默认都是用的非公平)

Lock lock = new ReentrantLock(true); // 创建一个公平锁

2.可重入锁

可重入锁(Reentrant Lock),也称为递归锁,是一种支持同一个线程多次获取同一个锁的锁机制。这意味着如果一个线程已经获得了某个锁,那么它可以多次再次获取该锁,而不会被阻塞。可重入锁允许线程在持有锁的情况下多次进入由这个锁保护的临界区域,而不会引发死锁或其他问题。

Java 中的 ReentrantLocksynchronized 关键字都是可重入锁的示例。以下是一个简单的示例,说明可重入锁的工作方式:

import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockExample {private static final ReentrantLock lock = new ReentrantLock();public static void main(String[] args) {lock.lock(); // 第一次获取锁try {System.out.println("First lock acquired.");lock.lock(); // 第二次获取锁,仍然允许try {System.out.println("Nested lock acquired.");} finally {lock.unlock(); // 释放第二次获取的锁System.out.println("Nested lock released.");}} finally {lock.unlock(); // 释放第一次获取的锁System.out.println("First lock released.");}}
}

3.自旋锁

自旋锁是一种用于多线程同步的锁机制,它不会让线程进入阻塞状态,而是在尝试获取锁时,如果锁已经被其他线程占用,它会一直循环(自旋)等待锁被释放,而不放弃 CPU 时间片。自旋锁主要用于短时间内锁的竞争情况,希望竞争线程在等待期间能够快速释放锁,从而减少线程切换的开销。

自旋锁的优点包括:

  1. 低开销: 自旋锁不涉及线程的上下文切换(Context Switching),因此在锁竞争不激烈的情况下,可以减少系统的开销。

  2. 等待时间短: 在短时间内,如果锁能够被释放,自旋锁可以快速获取锁,避免了进入阻塞状态的开销。

  3. 可预测性: 自旋锁的等待时间是可控的,不受操作系统调度器的影响,因此可以具有更可预测的性能。

自旋锁的缺点包括:

  1. 高竞争情况下效率低: 在高度竞争锁的情况下,自旋锁会让线程忙等,浪费 CPU 时间,效率较低。

  2. 不适用于长时间等待: 自旋锁适用于短时间内锁的竞争,如果等待时间过长,会导致 CPU 时间浪费,不适合长时间等待锁的情况。

在Java中,java.util.concurrent 包中提供了一种自旋锁的实现,称为 java.util.concurrent.atomic.AtomicReference,它可以用来构建简单的自旋锁。此外,Java中的 java.util.concurrent.locks 包中也提供了更复杂的锁实现,如 ReentrantLock,它可以通过参数来控制是否自旋等待。在使用自旋锁时,需要谨慎评估锁的使用场景,以确保它适合你的应用程序需求。

4.死锁排查

1.使用jps -l 定位进程号

2.使用jstack pid 查看堆栈信息。

可以看见,T1和T2互相持有了对方想要的锁。

排查问题: 1.看日志,2. 查看堆栈信息。

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

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

相关文章

【AI视野·今日Robot 机器人论文速览 第四十三期】Thu, 28 Sep 2023

AI视野今日CS.Robotics 机器人学论文速览 Thu, 28 Sep 2023 Totally 37 papers &#x1f449;上期速览✈更多精彩请移步主页 Interesting: &#x1f4da;****触觉力控学习策略,基于触觉的主动推理与力控用于小孔插入任务。提出了姿态控制与插入控制双策略模型。 (from 东京大学…

HTML开篇之安装VSvode(用记事本编辑HTML)

文章目录 前端开篇开篇知识点讲解1.HTML 结构1.1认识 HTML 标签1.2HTML 文件基本结构1.3标签层次结构1.4快速生成代码框架1.5用记事本写HTML1.6前端开发工具1.7下载vscode 及使用教学 大家好&#xff0c;我是晓星航。今天为大家带来的是 HTML 相关的讲解&#xff01;&#x1f6…

redis的简单使用

文章目录 环境安装与配置redis发布-订阅相关命令redis发布-订阅的客户端编程redis的订阅发布的例子 环境安装与配置 sudo apt-get install redis-server # ubuntu命令安装redis服务ubuntu通过上面命令安装完redis&#xff0c;会自动启动redis服务&#xff0c;通过ps命令确认&a…

【数组及指针经典笔试题解析】

1.数组和指针笔试题 题目1 int main(){int a[5] { 1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5};int * ptr (int * )(&a 1);printf("%d&#xff0c;%d"&#xff0c;*(a 1)&#xff0c;*(ptr - 1));return 0;}图文解析&#xff1a; int * ptr …

批量删除wordpress文章修订版本/自动草稿残留数据(3种方法)及四种方法禁用WordPress文章历史修订/自动保存/自动草稿功能

目录 1、批量删除wordpress文章修订版本/自动草稿残留数据&#xff08;3种方法&#xff09; 方法一&#xff1a;SQL命令批量删除 命令&#xff1a; 方法二&#xff1a;利用PHP代码来删除 方法三&#xff1a;利用数据库清理优化插件 WP Clean Up 或 WP Cleaner 批量删除 2…

PowerPoint如何设置密码?

PowerPoint&#xff0c;也就是PPT&#xff0c;是很多人工作中经常用的办公软件&#xff0c;而PPT和Word、Excel等一样可以设置密码保护。 PPT可以设置两种密码&#xff0c;一种是“打开密码”&#xff0c;也就是需要密码才能打开PPT&#xff1b;还有一种是设置成有密码的“只读…

Leetcode字符串题目

1 sslist(s) ttlist(t) ss.sort() tt.sort() return sstt 时间复杂度更低的代码 2 dict1{} dict2{} for ch in s:dict1[ch]dict1.get(ch,0)1 # 如果有ch&#xff0c;则原有位置加一&#xff0c;没有的话就创建了(01) for ch in t:dict2[ch]dict2.get(ch,0)1 return dict1…

【C语言】函数的定义、传参与调用(一)

目录 导读&#xff1a; 1. 为什么要用函数 2. C语言中函数的分类 2.1 库函数 2.1.1 什么是库函数 2.1.2 C语言常用的库函数 2.2 自定义函数 2.2.1 什么是自定义函数 2.2.2 定义函数的方法 2.2.3 举例 3. 函数的参数 3.1 传参不同的对比 3.2 形式参数&#xff08;形…

列表的增删改查和遍历

任务概念 什么是任务 任务是一个参数为指针&#xff0c;无法返回的函数&#xff0c;函数体为死循环不能返回任务的实现过程 每个任务是独立的&#xff0c;需要为任务分别分配栈称为任务栈&#xff0c;通常是预定义的全局数组&#xff0c;也可以是动态分配的一段内存空间&#…

腾讯云域名API解析升级版本(通过Java实现)腾讯云动态公网IP绑定域名实现内网服务器公网穿透

公众号推广: 目前CSDN进行VIP可见,文章可在微信公众号进行免费的阅读。 文章内容经过认证实践,比较的清晰易懂,适合初次接触的人员。 请关注微信公众号:菜鸟编程踩坑之路,进入公众号搜索关键词 内网穿透 需求场景: 首先我自己组装了一台自己的服务器,相比较购买的阿…

CVE-2020-11978 Apache Airflow 命令注入漏洞分析与利用

简介 漏洞软件&#xff1a;Apache Airflow影响版本&#xff1a;< 1.10.10 环境 Vulhub 漏洞测试靶场 复现步骤 进入 /root/vulhub/airflow/CVE-2020-11978/ 目录运行以下命令启动环境 # 初始化数据库 docker compose run airflow-init # 开启服务 docker compose up -…

计算机算法分析与设计(4)---凸多边形的最优三角划分(含C++代码)

文章目录 一、概述1.1 概念说明1.2 与矩阵连乘对应关系1.3 递归定义 二、代码 一、概述 1.1 概念说明 1. 用多边形顶点的逆时针序列表示凸多边形&#xff0c;即P{V0, V1, … Vn-1, Vn}表示具有n1条边的凸多边形。 2. 若Vi和Vj是多边形上不相邻的两个顶点&#xff0c;则线段ViV…

华为鸿蒙手表开发之动态生成二维码

华为鸿蒙手表开发之动态生成二维码 前言&#xff1a; 最近入职新公司&#xff0c;由于之前的哥们临时离职&#xff0c;走得很突然&#xff0c;所以没有任何交接和文档&#xff0c;临时顶上公司手表应用的上架&#xff0c;更换了新的密钥和key之后重新测试功能和流程&#xff…

unity 鼠标标记 左键长按生成标记右键长按清除标记,对象转化为子物体

linerender的标记参考 unity linerenderer在Game窗口中任意画线_游戏内编辑linerender-CSDN博客 让生成的标记转化为ARMarks游戏对象的子物体 LineMark.cs using System.Collections; using System.Collections.Generic; using UnityEngine;public class LineMark : MonoBeh…

【Docker】docker拉取镜像错误 missing signature key

问题 当我使用docker拉取一个特定的镜像时&#xff0c;提示错误&#xff1a; 错误 missing signature key 但是拉取其他镜像又可以访问&#xff0c;&#xff0c;&#xff0c;&#xff0c;于是&#xff0c;我怀疑是否是docker版本问题。 docker --version结果确实&#xff0…

【网络安全-sqlmap】sqlmap以及几款自动化sql注入工具的详细使用过程,超详细,SQL注入【5】

一&#xff0c;sqlmap 工具的详细使用 kali系统自带这个工具&#xff0c;无需安装直接sqlmap 后面接参数使用 Windows上参照以下方法安装即可 1-1 工具下载 1-1-1 sqlmap下载 sqlmap 工具下载地址&#xff1a; GitHub - sqlmapproject/sqlmap: Automatic SQL injection a…

获取网卡上的IP、网关及DNS信息,获取最佳路由,遍历路由表中的条目(附源码)

VC常用功能开发汇总&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持续更新...&#xff09;https://blog.csdn.net/chenlycly/article/details/124272585C软件异常排查从入门到精通系列教程&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持续更新...&a…

flutter开发实战-应用更新apk下载、安装apk、启动应用实现

flutter开发实战-应用更新apk下载、安装apk、启动应用实现 在开发过程中&#xff0c;经常遇到需要更新下载新版本的apk文件&#xff0c;之后进行应用更新apk下载、安装apk、启动应用。我们在flutter工程中实现下载apk&#xff0c;判断当前版本与需要更新安装的版本进行比对判断…

【强化算法专题一】双指针算法

【强化算法专题一】双指针算法 1.双指针算法--移动零2.双指针算法--复写零3.双指针算法--快乐数4.双指针算法--盛水最多的容器5.双指针算法--有效三角形的个数6.双指针算法--和为s的两个数7.双指针算法--三数之和8.双指针算法--四数之和 1.双指针算法–移动零 算法原理解析----…

普通人需要做副业吗?有什么合适的副业

普通人现在需要做副业吗&#xff0c;我觉得有这个必要&#xff0c;当然也要根据个人情况选择&#xff0c;那么做副业有什么好处呢&#xff1f;做副业可以带来额外的收入&#xff0c;增加灵活性&#xff0c;提升技能&#xff0c;发展创造力&#xff0c;降低风险&#xff0c;提供…