当前位置: 首页 > news >正文

佛山专业网站建设/指数型基金

佛山专业网站建设,指数型基金,杭州91网站建设,网页版微信小程序在哪作为一个Java开发,对于Synchronized这个关键字并不会陌生,无论是并发编程,还是与面试官对线,Synchronized可以说是必不可少。 在JDK1.6之前,都认为Synchronized是一个非常笨重的锁,就是在之前的《谈谈Java…

作为一个Java开发,对于Synchronized这个关键字并不会陌生,无论是并发编程,还是与面试官对线,Synchronized可以说是必不可少。

在JDK1.6之前,都认为Synchronized是一个非常笨重的锁,就是在之前的《谈谈Java中的锁》中提到的重量级锁。但是在JDK1.6对Synchronized进行优化后,Synchronized的性能已经得到了巨大提升,也算是脱下了重量级锁这一包袱。本文就来看看Synchronized的使用与原理。

JDK1.6后优化点:

锁粗化(Lock Coarsening)、锁消除(Lock Elimination)、轻量级锁(Lightweight Locking)、偏向锁(Biased Locking)、适应性自旋(Adaptive Spinning)等技术来减少锁操作的开销,synchronized的并发性能已经基本与J U C包提供的Lock持平

一、Synchronized的使用

在Java中,synchronized有:【修饰实例方法】、【修饰静态方法】、【修饰代码块】三种使用方式,分别锁住不同的对象。这三种方式,获取不同的锁,锁定共享资源代码段,达到互斥(mutualexclusion)效果,以此保证线程安全。

共享资源代码段又被称之为临界区,锁的作用就是保证临界区互斥,即同一时间临界区的只能有一个线程执行,其他线程阻塞等待,排队等待前一个线程释放锁

1.1 修饰实例方法

作用于当前对象实例加锁,进入同步代码前要获得 当前对象实例的锁

public synchronized void methodA() {System.out.println("作用于当前对象实例加锁,进入同步代码前要获得 当前对象实例的锁");
}

1.2 修饰静态方法

给当前类加锁,作用于当前类的所有对象实例,进入同步代码前要获得 当前 class 的锁

当被static修饰时,表明被修饰的代码块或者变量是整个类的一个静态资源,属于类成员,不属于任何一个实例对象,也就是说不管 new 了多少个对象,都只有一份,

public synchronized static void methodB() {System.out.println("给当前类加锁,作用于当前类的所有对象实例,进入同步代码前要获得 **当前 class 的锁**。");
}

如果一个线程 A 调用一个实例对象的非静态 synchronized 方法,而线程 B 需要调用这个实例对象所属类的静态 synchronized 方法,是不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。

1.3 修饰代码块

指定加锁对象,对给定对象/类加锁

synchronized(this|object) 表示进入同步代码库前要获得给定对象的锁。

public void methodc() {synchronized(this) {System.out.println("锁住的是当前对象,对象锁");}
}

synchronized(类.class) 表示进入同步代码前要获得 当前 class 的锁

javapublic void methodd() {synchronized(SynchronizedDome.class) {System.out.println("给当前类加锁,SynchronizedDome class 的锁");}
}

二、Synchronized案例说明

了解了Synchronized的使用方法以后,接下来结合案例的方式,来详细看看Synchronized的加锁,多线程下是怎么执行的,我这里将按照上面三个使用方法来分别使用案例描述

2.1 修饰实例方法案例

  • 案例说明两个线程同时对一个共享变量sum进行累加3000,输出其最终结果,我们期望的结果最终应该是6000,接下来看看不加Synchronized修饰和加Synchronized修饰的情况下分别输出什么。

2.1.1 案例

  • 不加Synchronized修饰
package com.upup.edwin.sync;public  class SynchronizedDome {//定义来了一个共享变量public static int sum = 0;// 进行累加3000次public void add() {for (int i = 0; i < 3000; i++) {sum = sum + 1;}}public static void main(String[] args) throws InterruptedException {SynchronizedDome dome = new SynchronizedDome();Thread thread1 = new Thread(() -> dome.add());Thread thread2 = new Thread(() -> dome.add());thread1.start();thread2.start();//join()  方法是让main线程等待线程 thread1 和thread2 都执行完成之后在继续执行下面的输出thread1.join();thread2.join();System.out.println("两个线程执行完成之后的累加结果:sum = " + sum);}}

从结果上看,当我们不加synchronized修饰的时候,输出结果并不是我们锁期待的6000,这说明两个线程之间在执行的时候相互干扰了,也就是线程不安全。

加Synchronized修饰

我们对上面的add方法进行改造,在方法上加上synchronized关键字,也就是加上了锁,来看看它的执行结果

// 进行累加3000次
public synchronized void add() {for (int i = 0; i < 3000; i++) {sum = sum + 1;}
}

加上synchronized修饰后,发现输出结果与我们预期的是一致的,说明加上锁,两个线程是排队顺序执行的

2.1.2 案例执行过程

通过以上的两个案例对比,可以发现在synchronized修饰方法的时候,能够让结果正常输出,保证了线程安全,那么它是怎么做到的吗,两个线程的执行过程是怎么样的呢?

在前面我们提到了,当synchronized修饰实例方法的时候获取的是当前对象实例的锁,我们在代码中new出了一个SynchronizedDome对象,因此本质上锁住的是这个对象

SynchronizedDome dome = new SynchronizedDome();

所有线程要执行同步函数都要先获取锁(synchronized里面叫做监视器锁),获取到锁的线程才能执行同步函数,没有获取到的线程只能等待,抢到锁的线程执行完同步函数后会释放锁并通知唤醒其他等待的线程再次获取锁。流程如下

2.2 修饰静态方法案例

修饰静态方法与修饰实例方法基本一致,唯一的区别就是锁的不是当前对象,而是整个Class对象。我们只需要把上述案例中同步函数改成静态的就可以了

package com.upup.edwin.sync;public  class SynchronizedDome {//定义来了一个共享变量public static int sum = 0;// 进行累加3000次public synchronized static void add() {for (int i = 0; i < 3000; i++) {sum = sum + 1;}}public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> add());Thread thread2 = new Thread(() -> add());thread1.start();thread2.start();//join()  方法是让main线程等待线程 thread1 和thread2 都执行完成之后在继续执行下面的输出thread1.join();thread2.join();System.out.println("两个线程执行完成之后的累加结果:sum = " + sum);}}

可以看到当我们改成静态方法之后,就不需要在main方法中new SynchronizedDome()了,直接调用add即可,这也说明锁的不是当前对象了,

我们知道在Java中静态资源是属于Class的,不属于任何一个实例对象,而每个Class对象在Jvm中都是唯一的,所以我们锁住Class对象后,其他线程无法获取其静态资源了,从而进入等待阶段,本质上,锁住静态资源的执行过程与锁住实例方法的执行过程是一致的,只是锁的对象不一样而已

2.3 修饰代码块案例

静态资源锁Class,实例方法锁对象,还有一种就是锁住一个方法的某一段代码,也就是代码块。比如我们在上述的add 方法中调用了一个print方法

 public static void print(){try {Thread.sleep(5000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("这是一个不需要加锁的方法,当前执行的线程是 : " + Thread.currentThread().getName());}

如上,print就是睡眠了5秒钟后输出一句话,不涉及到线程安全问题,如果使用synchronized修饰整个Add方法,并且在add中调用 print(),如下

public synchronized static void add() {print();for (int i = 0; i < 3000; i++) {sum = sum + 1;}
}

这种方式synchronized就会把add()整个包裹,使整个程序执行时间变长,完整案例如下

package com.upup.edwin.sync;public  class SynchronizedDome {//定义来了一个共享变量public static int sum = 0;// 进行累加3000次public synchronized static void add() {print();for (int i = 0; i < 3000; i++) {sum = sum + 1;}}// 可以异步执行的方法public static void print(){try {Thread.sleep(5000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("这是一个不需要加锁的方法,当前执行的线程是 : " + Thread.currentThread().getName());}public static void main(String[] args) throws InterruptedException {long l1 = System.currentTimeMillis();Thread thread1 = new Thread(() -> add());Thread thread2 = new Thread(() -> add());thread1.start();thread2.start();//join()  方法是让main线程等待线程 thread1 和thread2 都执行完成之后在继续执行下面的输出thread1.join();thread2.join();long l2 = System.currentTimeMillis();System.out.println("两个线程执行完成的时间是:l2 - l1 = " + (l2 - l1) + " 毫秒");System.out.println("两个线程执行完成之后的累加结果:sum = " + sum);}}

以上案例执行结果如下

很明显,两个线程在排队执行Add方法时,连print方法一起等待,但是实际上print是一个线程安全的方法,不需要获取锁,并且print方法还比较耗时,这就拖慢了整个程序的执行总时长,其执行过程如下

这种方式会将线程安全的方法也锁住,导致排队执行代码变多,时间变长,其本质就是synchronized锁住的是整个Add方法,粒度比较大,我们可以对add进行改造一下,让它只锁累计的那一段代码

public static void add() {print();synchronized(SynchronizedDome.class){for (int i = 0; i < 3000; i++) {sum = sum + 1;}}
}

如上,synchronized只锁了这for循环段代码,print()是可以并行执行的,这样就可以提升整个方法的执行效率,完整代码如下

package com.upup.edwin.sync;public  class SynchronizedDome {//定义来了一个共享变量public static int sum = 0;// 进行累加3000次public static void add() {print();synchronized(SynchronizedDome.class){for (int i = 0; i < 3000; i++) {sum = sum + 1;}}}// 可以异步执行的方法public static void print(){try {Thread.sleep(5000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("这是一个不需要加锁的方法,当前执行的线程是 : " + Thread.currentThread().getName());}public static void main(String[] args) throws InterruptedException {long l1 = System.currentTimeMillis();Thread thread1 = new Thread(() -> add());Thread thread2 = new Thread(() -> add());thread1.start();thread2.start();//join()  方法是让main线程等待线程 thread1 和thread2 都执行完成之后在继续执行下面的输出thread1.join();thread2.join();long l2 = System.currentTimeMillis();System.out.println("两个线程执行完成的时间是:l2 - l1 = " + (l2 - l1) + " 毫秒");System.out.println("两个线程执行完成之后的累加结果:sum = " + sum);}}

修改之后,整个方法的执行时间只有5秒多,我们休眠的时间也是5秒,说明两个线程是一起进入的休眠,并不是排队的,其执行过程如下

在上述案例中我使用的是锁住整个class的方法:‘synchronized(SynchronizedDome.class)’,如果要改成锁住对象只需要改成’synchronized(this)'即可。其他执行流程都是一样的,只是获取的锁不一样

三、Synchronized原理剖析

以Hotspot(是Jvm的一种实现)为例,在Jvm中每个Class都有一个对象,对象又由 【对象头 + 实例数据 + 对齐填充(java对象必须是8byte的倍数)】三部分组成,每个对象都有一个对象头,synchronized的锁就是存在对象头中的。

3.1 对象头

既然synchronized的锁是存在对象头中的,那就先来了解一下对象头,Hotspot 有两种对象头:

  • 数组类型:如果对象是数组类型,则虚拟机用3字节存储对象头
  • 非数组类型:如果对象是非数组类型,则用2字节存储对象头

一般对象头由两部分组成

  • Mark Word

存储自身的运行时数据,比如:对象的HashCode,分代年龄和锁标志位信息。

Mark Word存储的信息与对象自身定义无关,所以Mark Word是一个一个非固定的数据结构,Mark Word里存储的数据会在运行期间随着锁标志位的变化而变化。

  • Klass Pointer:类型指针指向它的类元数据的指针。

Mark Word在不同的虚拟机下的bit位不一样,以下是32位与64位虚拟机的对比图

3.2 Monitor

在了解Monitor之前,先思考一个问题,前面我们说synchronized修饰方法和代码块的时候,加锁业务流程的执行过程是一样的,那么他们内部加锁实现是不是一样的呢?

其实加锁过程肯定是不一样的,不然加锁过程一样,锁一样,加锁业务流程的执行过程是一样,那就没必要分成方法和代码块了

我们可以找到上述案例中的SynchronizedDome类的Class文件,然后再命令行中执行javap -c -s -v -l SynchronizedDome.class就可以看到编译后指令集。可以分别查看synchronized修饰方法和代码块指令集的区别

synchronized代码块

上图中的指令集中有monitorenter、monitorexit两个指令,当synchronized修饰代码块时,JVM就是使用monitorenter和monitorexit两个指令实现同步的,当线程执行到monitorenter的时候要先获得monitor锁,才能执行后面的方法。当线程执到monitorexit的时候则要释放锁。

synchronized修饰方法

上图中的指令集中有一个ACCSYNCHRONIZED标记,当synchronized修饰方法时,JVM通过在方法访问标识符(flags)中加入ACCSYNCHRONIZED来实现同步功能,当线程执行有ACCSYNCHRONIZED标志的方法,需要获得monitor锁。每个对象都与一个monitor相关联,线程可以占有或者释放monitor。

3.1什么是Monitor锁

从上面的描述无论是修饰代码块还是修饰方法,都要获取一个Monitor锁,那么什么是Monitor锁呢?

Monitor即监视器,可以理解为一个同步工具或一种同步机制,通常被描述为一个对象。每一个Java对象就有一把看不见的锁,称为内部锁或者Monitor锁

Monitor锁与对象的关系图:

任何一个对象都有一个Monitor与之关联,当且一个Monitor被持有后,它将处于锁定状态。Synchronized在JVM中基于进入和退出Monitor对象,通过成对的MonitorEnter和MonitorExit指令来实现方法同步和代码块同步。

  • MonitorEnter插入在同步代码块的开始位置,当代码执行到该指令时,将会尝试获取该对象Monitor锁;
  • MonitorExit:插入在方法结束处和异常处,JVM保证每个MonitorEnter必须有对应的MonitorExit,释放Monitor锁;

3.2 Monitor锁的工作原理

每一个对象都会有一个monitor锁,Monitor锁的MarkWord锁标识位为10,其中指针指向的是Monitor对象的起始地址。

在Java虚拟机(HotSpot)中,Monitor是由ObjectMonitor实现的,ObjectMonitor中维护了一个锁池(EntryList)和等待池(WaitSet)。

ObjectMonitor工作模型图如下:

ObjectMonitor工作模型图大致描述了以下几个步骤

所有新的线程都会进入(①号入口)EntryList中去竞争锁

当有线程通过CAS把monitor的owner字段设置为自己时,说明这个线程获取到了锁,也就是进入图中的(②号入口)owner区域,其他线程进入阻塞状态

如果当前线程是第一次进入该monitor,将recursions由0设置为1,_owner为当前线程,该线程成功获得锁并返回

如果当前线程不是第一次进入该monitor,说明当前线程再次进入monitor,即重入锁,执行recursions ++ ,记录重入的次数

如果获取到锁的线程(owner)执行了wait等方法,就会释放锁,并进入(③号入口)waitset中,于此同时通知waitset中其他线程重新竞争锁,获取到锁(④号入口)进入owner区域

当线程执行完同步代码,会释放锁(由⑤号口出),于此同时通知waitset和EntryList中其他线程重新竞争锁

释放锁线程执行monitorexit,monitor的进入数-1,执行过多少次monitorenter,最终要执行对应次数的monitorexit

四、Synchronized锁优化

接下来看看Synchronized的锁优化。锁优化主要包含:锁粗化、锁消除、锁升级三部分。

4.1 锁粗化

同步代码块要求我们将同步代码的范围尽量缩小,这样可以使同步的操作数量尽可能缩小,缩短阻塞时间,如果存在锁竞争,那么等待锁的线程也能尽快拿到锁

比如上述案例add的循环中,如果将Synchronized防止for循环里面不是范围更小吗?

for (int i = 0; i < 3000; i++) {synchronized(SynchronizedDome.class){sum = sum + 1;}
}

这样虽然缩小了范围,但是未必缩短了时间,因为在加锁过程中也会消耗资源,如果频繁的加锁释放锁,可能会导致性能损耗。

基于此,JVM会对这种情况进行锁粗化,锁粗化就是将【多个连续的加锁、解锁操作连接在一起】,扩展成一个范围更大的锁,避免频繁的加锁解锁操作。

J V M在检测到上述for循环再频繁获取同一把锁的到时候,就会将加锁的范围粗化到循环操作的外部,使其只需要获取一次锁就可以,减小加锁释放锁的开销。

4.2 锁消除

Java虚拟机在JIT编译时,会进行逃逸分析(对象在函数中被使用,也可能被外部函数所引用,称为函数逃逸),通过对运行上下文的扫描,分析synchronized锁对象是不是只被一个线程加锁,不存在其他线程来竞争加锁的情况。这样就可以消除该锁了,提升执行效率。

锁消除的经典案例就是StringBuffer 了,StringBuffer 是线程安全的,其内部的append方法就是通过synchronized加锁的,源码如下

@Override
public synchronized StringBuffer append(String str) {toStringCache = null;super.append(str);return this;
}

当我们调用StringBuffer 的append时,就会加锁,但是当我们使用的对象经过逃逸分析后,认为该对象不会被其他线程共享的时候,就会将append方法的synchronized去掉,编译不加入monitorenter和monitorexit指令。比如下面这个方法

public static String appendStr(String str, int i) {StringBuffer sb= new StringBuffer();sb.append(str);sb.append(i);return sb.toString();
}

StringBuffer的append虽然是同步方法。但appendStr中的sb对象没有传递到方法外,不会被其他线程引用,不存在锁竞争的情况,因此可以进行锁消除

五、Synchronized锁升级

我们常说的锁升级其实就是这几种锁的升级跃迁。其中有无锁、偏向锁 、轻量级锁、 重量级锁等几种锁的实现。锁升级过程:【无锁】—>【偏向锁】—>【轻量级锁】—>【 重量级锁】。

而锁的变化其实就是一个标志位的变化,在前面提到的对象头中Mark World时有提到它存储的就是对象的HashCode,分代年龄和锁标志位信息。因此锁的升级变化,本质上就是Mark World中锁标志位的变化。以上几种锁的标志位信息如下

锁状态存储内容存储内容
无锁对象的hashCode、对象分代年龄、是否是偏向锁(0)01
偏向锁偏向线程ID、偏向时间戳、对象分代年龄、是否是偏向锁(1)01
轻量级锁指向栈中锁记录的指针00
重量级锁指向互斥量(重量级锁)的指针10

注意:

锁可以升级不可以降级,但是偏向锁状态可以被重置为无锁状态!!!

锁可以升级不可以降级,但是偏向锁状态可以被重置为无锁状态!!!

锁可以升级不可以降级,但是偏向锁状态可以被重置为无锁状态!!!

5.1 无锁升级为偏向锁

  • 为啥要有偏向锁

大多数情况下是一个线程多次获得同一个锁,不存在锁竞争的,而竞争锁会增大资源消耗,,为了降低获取锁的代价,才引入的偏向锁。

当线程第一次执行到同步代码块的时候,锁对象变成就会偏向锁(通过CAS修改对象头里的锁标志位),其目标就是在只有一个线程执行同步代码块时,降低获取锁带来的消耗,提高性能

偏向锁是默认开启的,而且开始时间一般是比应用程序启动慢几秒,可以通过JVM配置成没有延迟

-XX:BiasedLockingStartUpDelay=0

可以通过J V M参数关闭偏向锁,关闭之后程序默认会进入轻量级锁状态

-XX:-UseBiasedLocking=false

无锁升级为偏向锁,其本质是判断对象头的Mark Word中线程ID与当前线程ID是否一致以及偏向锁标识,如果一致直接执行同步代码或方法,具体流程如下

无锁状态,存储内容「是否为偏向锁(0)」,锁标识位01

CAS设置当前线程ID到Mark Word存储内容中,并且将是否为偏向锁0 修改为 是否为偏向锁1

在Mark Word和栈帧中记录获取到偏向的锁的threadID

执行同步代码或方法

偏向锁状态,存储内容「是否为偏向锁(1)、线程ID」,锁标识位01

对比线程ID是否一致,如果一致无需使用CAS来加锁、解锁,直接执行同步代码或方法

因为偏向锁不会自动释放锁,因此后续线程A再次获取锁的时候,需要比较当前线程的threadID和Java对象头中的threadID是否一致

如果不一致,CAS将Mark Word的线程ID设置为当前线程ID,设置成功,执行同步代码或方法

其他线程,如线程B要竞争锁对象,而偏向锁不会主动释放,因此Mark Word还是存储的线程A的threadID

此时会检查Mark Word的线程A是否存活,如果没有存活,那么锁对象被重置为无锁状态,其它线程(线程B)可以竞争将其设置为偏向锁;

CAS设置失败,证明存在多线程竞争情况,触发撤销偏向锁,当到达全局安全点,偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后在安全点的位置恢复继续往下执行。

如果Mark Word的线程A是存活,则线程B的CAS会失败,此时会暂停线程A,撤销偏向锁,升级为轻量级锁,

5.2 偏向锁升级为轻量级锁

轻量级锁又称自旋锁,一般在竞争锁对象的线程比较少,持有锁时间也不长的场景中,由于阻塞线程、唤醒线程需要C P U从用户态转到内核态,时间比较长,如果同步代码块执行的时间比这更时间短,那就本末倒置了,所以这种情况一般不阻塞线程,让其自旋一段时间等待锁其他线程释放锁,通过自旋换取线程在用户态和内核态之间切换的开销。

锁竞争

如果多个线程轮流获取一个锁,但是每次获取锁的时候没有发生阻塞,就不存在锁竞争。只有当某线程尝试获取锁的时候,发现该锁已经被占用,只能等待其释放,这才发生了锁竞争。

当前线程持有的锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。

升级为轻量级锁有两种情况:

当关闭偏向锁功能时,会由无锁直接升级为轻量级锁

多个线程竞争偏向锁导致偏向锁升级为轻量级锁

这两种情况下偏向锁升级为轻量级锁过程如下

无锁状态:存储内容「是否为偏向锁(0)」,锁标识位01

关闭偏向锁功能时
  • CAS设置当前线程栈中锁记录的指针到Mark Word存储内容
  • 锁标识位设置为00
  • 执行同步代码或方法

轻量级锁状态:存储内容「线程栈中锁记录的指针」,锁标识位00

  • CAS设置当前线程栈中锁记录的指针到Mark Word存储内容,设置成功获取轻量级锁,执行同步块代码或方法
  • 设置失败,证明多线程存在一定竞争,线程自旋上一步的操作,自旋一定次数后还是失败,轻量级锁升级为重量级锁
  • Mark Word存储内容替换成重量级锁指针,锁标记位10

5.3 轻量级锁升级为重量级锁

轻量级锁在自旋一定次数之后还没获取到锁,就升级为重量级锁,重量级锁是依赖操作系统的MutexLock(互斥锁)来实现的,需要从用户态转到内核态,成本非常高,等待锁的线程都会进入阻塞状态,防止CPU空转。

计数器记录自旋次数,默认允许循环10次,可以通过虚拟机参数更改

相关文章:

从根本上理解Synchronized的加锁过程

作为一个Java开发&#xff0c;对于Synchronized这个关键字并不会陌生&#xff0c;无论是并发编程&#xff0c;还是与面试官对线&#xff0c;Synchronized可以说是必不可少。 在JDK1.6之前&#xff0c;都认为Synchronized是一个非常笨重的锁&#xff0c;就是在之前的《谈谈Java…...

CANOE入门到精通——CANOE系列教程记录1 第一个仿真工程

本系列以初学者角度记录学习CANOE&#xff0c;以《CANoe开发从入门到精通》参考学习&#xff0c;CANoe16 demo版就可以进行学习 概念 CANoe是一种用于开发、测试和分析汽车电子系统的软件工具。它通过在不同层次上模拟汽车电子系统中的不同部件&#xff0c;如ECU、总线和传感…...

JavaEE——单例模式

文章目录 一、介绍什么是单例模式二、饿汉模式三、懒汉模式四、讨论两种模式的线程安全问题 一、介绍什么是单例模式 在介绍单例模式之前&#xff0c;我们得先明确一个名词设计模式。 所谓设计模式其实不难理解&#xff0c;就是在计算机这个圈子中&#xff0c;呢些大佬们为了…...

关于数据倾斜

1、数据倾斜表现 1.1 hadoop中的数据倾斜表现 有一个多几个Reduce卡住&#xff0c;卡在99.99%&#xff0c;一直不能结束。各种container报错OOM异常的Reducer读写的数据量极大&#xff0c;至少远远超过其它正常的Reducer伴随着数据倾斜&#xff0c;会出现任务被kill等各种诡异…...

Shell第一次作业

要求&#xff1a; 1、判断当前磁盘剩余空间是否有20G&#xff0c;如果小于20G&#xff0c;则将报警邮件发送给管理员&#xff0c;每天检查一次磁盘剩余空间。 ​2、判断web服务是否运行&#xff08;1、查看进程的方式判断该程序是否运行&#xff0c;2、通过查看端口的方式判断…...

实例解读nn.AdaptiveAvgPool2d((1, 1))

nn.AdaptiveAvgPool2d((1, 1))在PyTorch中创建一个AdaptiveAvgPool2d类的实例。该类在输入张量上执行2D自适应平均池化。 自适应平均池化是一种池化操作&#xff0c;它计算每个输入子区域的平均值并产生一个指定大小的输出张量。子区域的大小是根据输入张量的大小和输出张量的…...

泛型编程 之模板(template)

C另一种编程思想称为 泛型编程&#xff0c;主要利用的技术就是模板 目录 C另一种编程思想称为 泛型编程&#xff0c;主要利用的技术就是模板 一、概念 二、函数模板 1、语法与使用&#xff1a; 2、函数模板注意事项 3、普通函数与函数模板的区别 4、普通函数与函数模板的调用规…...

用ChatGPT问DotNet的相关问题,发现DotNet工程师的前景还不错

本人最近费了九牛二虎之力注册了一个ChatGPT账号&#xff0c;现在就给大家分享一下&#xff0c;问一下关于.NET的问题&#xff0c;看看ChatGPT的AI功能具体如何&#xff1f; 一、C#跟其它语言比较的优势 回答&#xff1a; C#是一门编程语言&#xff0c;它是为 Microsoft 的 …...

LeetCode_字符串_简单_415.字符串相加

目录 1.题目2.思路3.代码实现&#xff08;Java&#xff09; 1.题目 给定两个字符串形式的非负整数 num1 和num2&#xff0c;计算它们的和并同样以字符串形式返回。 你不能使用任何內建的用于处理大整数的库&#xff08;比如 BigInteger&#xff09;&#xff0c; 也不能直接将…...

Insix:面向真实的生成数据增强,用于Nuclei实例分割

文章目录 InsMix: Towards Realistic Generative Data Augmentation for Nuclei Instance Segmentation摘要本文方法数据增强方法具有形态学约束的前景增强提高鲁棒性的背景扰动 实验结果 InsMix: Towards Realistic Generative Data Augmentation for Nuclei Instance Segment…...

CleanMyMac X4.13.2最新版下载

现在cleanmymac x4.13.2中文版是大家首选的优秀mac清理软件。CleanMyMac集合了多种功能&#xff0c;几乎可以满足用户所有的清洁需求。它不仅包含各种清理功能&#xff0c;还具有卸载、维护、扩展、碎纸机等实用功能&#xff0c;可同时替代多种工具。它可以清理、优化、维护和监…...

机器学习算法原理:详细介绍各种机器学习算法的原理、优缺点和适用场景

目录 引言 二、线性回归 三、逻辑回归 四、支持向量机 五、决策树 六、随机森林 七、K-均值聚类 八、主成分分析&#xff08;PCA&#xff09; 九、K近邻算法 十、朴素贝叶斯分类器 十一、神经网络 十二、AdaBoost 十三、梯度提升树&#xff08;Gradient Boosting T…...

Spring Security 6.0系列【32】授权服务器篇之默认过滤器

有道无术,术尚可求,有术无道,止于术。 本系列Spring Boot 版本 3.0.4 本系列Spring Security 版本 6.0.2 本系列Spring Authorization Server 版本 1.0.2 源码地址:https://gitee.com/pearl-organization/study-spring-security-demo 文章目录 前言1. OAuth2Authorizati…...

.NET中比肩System.Text.Json序列化反序列化组件MessagePack

简介 官方定义&#xff1a;MessagePack是一种高效的二进制序列化格式。它允许您像JSON一样在多个语言之间交换数据。但是它更快并且更小。 MessagePack是一种开源的序列化反序列化组件&#xff0c;可支持JAVA&#xff0c;C#等主流语言。在 C# 中使用 MessagePack&#xff0c…...

Oracle删除列操作:逻辑删除和物理删除

概念 逻辑删除&#xff1a;逻辑删除并不是真正的删除&#xff0c;而是将表中列所对应的状态字段&#xff08;status&#xff09;做修改操作&#xff0c;实际上并未删除目标列数据或恢复这些列占用的磁盘空间。比如0是未删除&#xff0c;1是删除。在逻辑上数据是被删除了&#…...

找出字符串中第一个匹配项的下标、求解方程----2023/5/2

找出字符串中第一个匹配项的下标、求解方程----2023/5/2 给你两个字符串 haystack 和 needle &#xff0c;请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标&#xff08;下标从 0 开始&#xff09;。如果 needle 不是 haystack 的一部分&#xff0c;则返回 -1…...

23:宁以non-member、non-friend替换member函数

想象有个class用来表示网页浏览器。这样的class可能提供的众多函数中&#xff0c;有一些用来清除下载元素高速缓存区、清除访问过的URLs的历史记录、以及移除系统中的所有cookies&#xff1a; class WebBrowser{ public:void clearCache();void clearHistory();void removeCoo…...

Centos7安装Redis

一、安装gcc依赖 由于 redis 是用 C 语言开发&#xff0c;安装之前必先确认是否安装 gcc 环境&#xff08;gcc -v&#xff09;&#xff0c;如果没有安装&#xff0c;执行以下命令进行安装 [rootlocalhost local]# yum install -y gcc 二、下载并解压安装包 [rootlocalhost l…...

Android 项目必备(四十五)-->2023 年如何构建 Android 应用程序

Android 是什么 Android 是一种基于 Linux 内核并由 Google 开发的开源操作系统。它用于各种设备包括智能手机、平板电脑、电视和智能手表。 目前&#xff0c;Android 是世界上移动设备使用最多的操作系统; 根据 statcounter 的一份最近 12 个月的样本报告;Android 的市场份额…...

改进YOLOv5: | 涨点神器 | 即插即用| ICLR 2022!Intel提出ODConv:即插即用的动态卷积

OMNI-DIMENSIONAL DYNAMIC CONVOLUTION ODConv实验核心代码ODConv代码yaml文件运行:论文链接: https://openreview.net/forum?id=DmpCfq6Mg39 本文介绍了一篇动态卷积的工作:ODConv,其通过并行策略采用多维注意力机制沿核空间的四个维度学习互补性注意力。作为一种“即插…...

( 数组和矩阵) 485. 最大连续 1 的个数 ——【Leetcode每日一题】

❓485. 最大连续 1 的个数 难度&#xff1a;简单 给定一个二进制数组 nums &#xff0c; 计算其中最大连续 1 的个数。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,0,1,1,1] 输出&#xff1a;3 解释&#xff1a;开头的两位和最后的三位都是连续 1 &#xff0c;所以最大…...

从0搭建Vue3组件库(十一): 集成项目的编程规范工具链(ESlint+Prettier+Stylelint)

欲先善其事,必先利其器。一个好的项目是必须要有一个统一的规范,比如代码规范,样式规范以及代码提交规范等。统一的代码规范旨在增强团队开发协作、提高代码质量和打造开发基石,所以每个人必须严格遵守。 本篇文章将引入 ESLintPrettierStylelint 来对代码规范化。 ESlint ES…...

Mysql 苞米豆 多数据源 读写分离(小项目可用)

目录 0 课程视频 1 配置 1.1 加依赖 1.2 yml 配置文件 -> druid配置后报错 搞不定 2 代码 2.1 实体类 2.2 mapper -> 调用操作数据库方法 操作数据库 2.3 service -> 指定数据源 -> 用Mapper 接口 -> 操作数据库 2.4 controller -> 用户使用接口 -&…...

OJ练习第90题——删除字符使频率相同

删除字符使频率相同 力扣链接&#xff1a;2423. 删除字符使频率相同 题目描述 给你一个下标从 0 开始的字符串 word &#xff0c;字符串只包含小写英文字母。你需要选择 一个 下标并 删除 下标处的字符&#xff0c;使得 word 中剩余每个字母出现 频率 相同。 如果删除一个字…...

云原生Istio基本介绍

目录 1 什么是Istio2 Istio特征2.1 连接2.2 安全2.3 策略2.4 观察 3 Istio与服务治理3.1服务治理的三种形态 4 Istio与Kubernetes4.1 Kubernetes介绍4.2 Istio是Kubernetes的好帮手4.3 Kubernetes是Istio的好基座 5 Istio与服务网格5.1 时代选择服务网格5.2 服务网格选择Istio …...

Vue(简单了解Cookie、生命周期)

一、了解Cookie 类似于对象响应携带数据 输入用户名密码跳转到指定页面 点击指定页面中其中一个按钮跳转到另一个指定页面&#xff08;再不需用输入用户名密码&#xff09; 例如现在很多浏览器实现七天免密登录 简单理解&#xff1a;就是在网站登录页面之后&#xff0c;服务…...

57.网页设计图标实战

首先我们需要找一个图标库&#xff0c;本次演示采用的是heroicon ● 之后我们根据需求搜索与之想匹配的图标并复制svg代码 ● 之后将我们的代码复制到我们想要放置图标的地方 ● 当然我们需要使用CSS来修饰一下 .features-icon {stroke: #087f5b;width: 32px;height: 3…...

浅析AI视频智能检测技术在城市管理中的场景应用

随着中国的城市建设和发展日益加快&#xff0c;城镇化过程中重建设、轻管理模式带来不少管理难点&#xff0c;传统城管模式存在违法问题多样、缺乏源头治理、业务协同难、取证手段单一等&#xff0c;人员不足问题进一步加剧管理难度。随着移动互联网、物联网、云计算、大数据、…...

unity中的Line Renderer

介绍 unity中的Line Renderer 方法 首先&#xff0c;Line Renderer 是 Unity 引擎中的一个组件&#xff0c;它可以生成直线、曲线等形状&#xff0c;并且在场景中呈现。通常情况下&#xff0c;Line Renderer 被用来实现轨迹、路径、线框渲染以及射线可视化等功能。 在使用 …...

【数据架构系列-06】一文搞懂数据模型的3中类型——概念模型、逻辑模型、物理模型

数据模型就是模拟现实世界的方法论&#xff0c;是通向智慧世界的基石&#xff01; 从现实世界发展到智慧世界&#xff0c;要数经历现实世界、信息世界、计算机世界、数据世界、智慧世界五个不同的世界&#xff0c;我们天生具有从混沌的世界抽象信息变为信息世界的能力&#xff…...