138.【JUC并发编程- 03】
JUC并发编程- 03
- (六)、共享模型之无锁
- 1.问题提出
- (1).为什么不安全?
- (2).安全实现_使用锁
- (3).安全实现_使用CAS
- 2.CAS与volatile
- (1).CAS_原理介绍
- (2).CAS_Debug分析
- (3).volatile
- (4).为什么无锁效率高
- (5).CAS的特点
- 3.原子整形
- (1).原子整数类型_ 自增自减
- (2).原子整数类型_乘除模
- 4. 原子引用类型
- (1).不安全的实现
- (2).安全实现_使用锁
- (3).安全实现_使用 CAS (AtomicReference)
- (4).ABA 问题
- (5).ABA问题解决1_AtomicStampedReference
- (6).ABA问题解决2_AtomicMarkableReference
- 5.原子数组
- (1).不安全的实现
- (2).安全实现_ 使用CAS
- 6.字段更新器
- (1).未使用voliatile修饰的时候会报异常
- (2).使用voliatile修饰后
- (3).存在问题ABA问题
- 7.原子累加器
- (1).运用累加器类性能更高
- (2).为什么性能高呢?
- (4).源码之 LongAdder
- (5).cas 锁源码
- (6).原理之伪共享
- (7).add源码
- 8.UnSafe
- (1).概述
- (2).获取UnSafe实列
- (3).UnSafe的Case操作
- (4).模拟原子整数
- (七)、共享模型之不可变
- 1.日期转换的问题
- (1).问题提出
- (2).思路 - 同步锁
- (3).思路 - 不可变
- 2.不可变设计
- (1).final 的使用
- (2).保护性拷贝
- 3.享元模式
- (1). 简介
- (2).体现
- (3).DIY
- 4.finnal原理
- (1).设置 final 变量的原理
- (2).获取final 变量的原理
- 5.无状态
(六)、共享模型之无锁
- CAS 与 volatile
- 原子整数
- 原子引用
- 原子累加器
- Unsafe
1.问题提出
有如下需求,能保证 account.withdraw 取款方法的线程安全嘛?
package com.jsxs.Test;import java.util.ArrayList;
import java.util.List;/*** @Author Jsxs* @Date 2023/10/11 14:58* @PackageName:com.jsxs.Test* @ClassName: Account* @Description: TODO* @Version 1.0*/
public interface Account {// 获取余额Integer getBalance();// 取款void withdraw(Integer amount);/*** 方法内会启动 1000 个线程,每个线程做 -10 元 的操作* 如果初始余额为 10000 那么正确的结果应当是 0*/static void demo(Account account) {List<Thread> ts = new ArrayList<>();long start = System.nanoTime();for (int i = 0; i < 1000; i++) {ts.add(new Thread(() -> {account.withdraw(10);}));}ts.forEach(Thread::start);ts.forEach(t -> {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}});long end = System.nanoTime();System.out.println(account.getBalance()+ " cost: " + (end - start) / 1000_000 + " ms");}
}
原有实现并不是线程安全的
package com.jsxs.Test;/*** @Author Jsxs* @Date 2023/10/11 14:59* @PackageName:com.jsxs.Test* @ClassName: AccountUnsafe* @Description: TODO* @Version 1.0*/
public class AccountUnsafe implements Account{private Integer balance;public AccountUnsafe(Integer balance) {this.balance = balance;}@Overridepublic Integer getBalance() {return balance;}@Overridepublic void withdraw(Integer amount) {balance -= amount;}// 进行测试public static void main(String[] args) {Account.demo(new AccountUnsafe(10000));}
}
某次的执行结果
(1).为什么不安全?
withdraw
方法: 对共享资源产生了竞态条件。
public void withdraw(Integer amount) {balance -= amount;}
对应的字节码
ALOAD 0 // <- this
ALOAD 0
GETFIELD cn/itcast/AccountUnsafe.balance : Ljava/lang/Integer; // <- this.balance
INVOKEVIRTUAL java/lang/Integer.intValue ()I // 拆箱
ALOAD 1 // <- amount
INVOKEVIRTUAL java/lang/Integer.intValue ()I // 拆箱
ISUB // 减法
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer; // 结果装箱
PUTFIELD cn/itcast/AccountUnsafe.balance : Ljava/lang/Integer; // -> this.balance
多线程执行流程
ALOAD 0 // thread-0 <- this
ALOAD 0
GETFIELD cn/itcast/AccountUnsafe.balance // thread-0 <- this.balance
INVOKEVIRTUAL java/lang/Integer.intValue // thread-0 拆箱
ALOAD 1 // thread-0 <- amount
INVOKEVIRTUAL java/lang/Integer.intValue // thread-0 拆箱
ISUB // thread-0 减法
INVOKESTATIC java/lang/Integer.valueOf // thread-0 结果装箱
PUTFIELD cn/itcast/AccountUnsafe.balance // thread-0 -> this.balance ALOAD 0 // thread-1 <- this
ALOAD 0
GETFIELD cn/itcast/AccountUnsafe.balance // thread-1 <- this.balance
INVOKEVIRTUAL java/lang/Integer.intValue // thread-1 拆箱
ALOAD 1 // thread-1 <- amount
INVOKEVIRTUAL java/lang/Integer.intValue // thread-1 拆箱
ISUB // thread-1 减法
INVOKESTATIC java/lang/Integer.valueOf // thread-1 结果装箱
PUTFIELD cn/itcast/AccountUnsafe.balance // thread-1 -> this.balance
(2).安全实现_使用锁
首先想到的是给 Account 对象加锁
(3).安全实现_使用CAS
package com.jsxs.Test;import java.util.concurrent.atomic.AtomicInteger;/*** @Author Jsxs* @Date 2023/10/11 15:15* @PackageName:com.jsxs.Test* @ClassName: AccountSafe* @Description: TODO* @Version 1.0*/
public class AccountSafe implements Account {// 1. AtomicInteger 原子性整形 ⭐private AtomicInteger balance;public AccountSafe(Integer balance) {this.balance = new AtomicInteger(balance); // ⭐⭐ 通过AtomicInteger 进行构造}@Overridepublic Integer getBalance() {return balance.get(); // ⭐⭐⭐ 调用原子类的get方法}@Overridepublic void withdraw(Integer amount) {while (true) {// 获取余额的最新值 调用get方法 ⭐⭐⭐int prev = balance.get();// 修改后的余额=余额的最新值减去取款金额 ⭐⭐⭐⭐int next = prev - amount;// 局部变量都是存储在线程的工作内存上(并不是主存上), 调用这个方法就是讲内存和主存同步 ⭐⭐⭐⭐⭐if (balance.compareAndSet(prev, next)) { // 第一个参数: 修改前的值,第二个参数: 修改后的值break;}}// 可以简化为下面的方法// balance.addAndGet(-1 * amount);}public static void main(String[] args) {Account.demo(new AccountSafe(10000));}
}
2.CAS与volatile
(1).CAS_原理介绍
前面看到的 AtomicInteger
的解决方法,内部并没有用锁来保护共享变量的线程安全。那么它是如何实现的呢?
public void withdraw(Integer amount) {// 需要不断尝试,直到成功为止while (true) {// 比如拿到了旧值 1000int prev = balance.get();// 在这个基础上 1000-10 = 990int next = prev - amount;/*compareAndSet 正是做这个检查,在 set 前,先比较 prev 与当前值- 不一致了,next 作废,返回 false 表示失败比如,别的线程已经做了减法,当前值已经被减成了 990那么本线程的这次 990 就作废了,进入 while 下次循环重试- 一致,以 next 设置为新值,返回 true 表示成功*/if (balance.compareAndSet(prev, next)) {break;}}}
其中的关键是 compareAndSet,它的简称就是 CAS (也有 Compare And Swap 的说法),它必须是原子操作。
解释:
比如说线程1先获取余额为100,然后减去10结果为90,此时还没有来得及进行cas的操作,线程2已经把原来100的值修改为了90。经比较线程2的将prev90更新到主存,主存prev为90与线程1获取的prev值100是不相等,那么返回fasle,线程1再次进行重新-10操作.
注意
-
其实
CAS
的底层是lock cmpxchg
指令(X86 架构),在单核 CPU 和多核 CPU 下都能够保证【比较-交换
】的原子性。 -
在多核状态下,某个核执行到带
lock
的指令时,CPU 会让总线锁住,当这个核把此指令执行完毕,再开启总线。这个过程中不会被线程的调度机制所打断,保证了多个线程对内存操作的准确性,是原子的。
(2).CAS_Debug分析
1. 设置线程数为1,然后在CAS的地方进行断点
2.查看断点达到的信息,并且给共享变量赋值9000
3.获取最新值再次比较,如果其他线程没有再次改变那么就返回true
(3).volatile
获取共享变量时,为了保证该变量的可见性,需要使用 volatile 修饰。
它可以用来修饰成员变量
和静态成员变量
,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存。即一个线程对 volatile
变量的修改,对另一个线程可见。
注意
volatile 仅仅保证了共享变量的可见性,让其它线程能够看到最新值,也能保证程序的有序性,但不能解决指令交错问题(不能保证原子性)
CAS 必须借助 volatile 才能读取到共享变量的最新值来实现【比较并交换】的效果
(4).为什么无锁效率高
- 无锁情况下,即使重试失败,线程始终在高速运行,没有停歇,而 synchronized 会让线程在没有获得锁的时候,发生上下文切换,进入阻塞。
- 打个比喻:线程就好像高速跑道上的赛车,高速运行时,速度超快,一旦发生上下文切换,就好比赛车要减速、熄火,等被唤醒又得重新打火、启动、加速… 恢复到高速运行,代价比较大
- 但无锁情况下,因为线程要保持运行,需要额外 CPU 的支持,CPU 在这里就好比高速跑道,没有额外的跑道,线程想高速运行也无从谈起,虽然不会进入阻塞,但由于没有分到时间片,仍然会进入可运行状态,还是会导致上下文切换。
线程数不超过CPU的核定线程数都很高效,假如超过了效率就会大幅降低。
(5).CAS的特点
结合 CAS
和 volatile
可以实现无锁并发
,适用于线程数少、多核 CPU 的场景下。
- CAS 是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗。
- synchronized 是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。
- CAS 体现的是无锁并发、无阻塞并发,请仔细体会这两句话的意思
- 因为没有使用 synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一。
- 但如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响。
3.原子整形
J.U.C 并发包提供了:
- AtomicBoolean
- AtomicInteger
- AtomicLong
以 AtomicInteger 为例
(1).原子整数类型_ 自增自减
原子类的所有方法都是线程安全的,都具有原子性且CAS自旋的操作。所以刚才的取账问题我们可以用 addAndGet() 方法进行替换。
package com.jsxs.Test;import java.util.concurrent.atomic.AtomicInteger;/*** @Author Jsxs* @Date 2023/10/11 17:11* @PackageName:com.jsxs.Test* @ClassName: Test09* @Description: TODO* @Version 1.0*/
public class Test09 {public static void main(String[] args) {AtomicInteger integer = new AtomicInteger(1);System.out.println("先获取再自增的操作" + integer.getAndIncrement()); // i++System.out.println("先自增再获取的操作:" + integer.incrementAndGet()); // ++iSystem.out.println("先自减再获取的操作:"+integer.decrementAndGet()); // --iSystem.out.println("先获取再自检的操作:"+integer.getAndDecrement()); // i--System.out.println("最终得值为:"+integer);System.out.println("================================");System.out.println("自定义后增加:"+integer.getAndAdd(4)); // 先获取再+4System.out.println("最终得值为:"+integer);System.out.println("自定义先增加:"+integer.addAndGet(4)); // 先自增4 在获取System.out.println("最终得值为:"+integer);System.out.println("================================");}
}
默认都是cas自旋的操作
(2).原子整数类型_乘除模
package com.jsxs.Test;import java.util.concurrent.atomic.AtomicInteger;/*** @Author Jsxs* @Date 2023/10/11 17:11* @PackageName:com.jsxs.Test* @ClassName: Test09* @Description: TODO* @Version 1.0*/
public class Test09 {public static void main(String[] args) {AtomicInteger integer = new AtomicInteger(1);// 这里是一个接口类型返回值类型是int,且参数类型是int, value是读取到的值, value*10是设置的值 会发生CAS自旋的操作System.out.println(integer.updateAndGet((value)->{return value*10;})); // 先修改在获取之System.out.println(integer.getAndUpdate((value)->{return value*10;})); // 先获取值再修改}
}
默认都是CAS自旋的操作
4. 原子引用类型
为什么需要原子引用类型?
- AtomicReference
- AtomicMarkableReference
- AtomicStampedReference
有如下方法
package com.jsxs.Test;import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;/*** @Author Jsxs* @Date 2023/10/11 19:28* @PackageName:com.jsxs.Test* @ClassName: DecimalAccount* @Description: TODO* @Version 1.0*/
public interface DecimalAccount {// 获取余额: 钱币类型BigDecimal getBalance();// 取款void withdraw(BigDecimal amount);/*** 方法内会启动 1000 个线程,每个线程做 -10 元 的操作* 如果初始余额为 10000 那么正确的结果应当是 0*/static void demo(DecimalAccount account) {List<Thread> ts = new ArrayList<>();for (int i = 0; i < 1000; i++) {ts.add(new Thread(() -> {account.withdraw(BigDecimal.TEN);}));}ts.forEach(Thread::start);ts.forEach(t -> {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}});System.out.println(account.getBalance());}
}
(1).不安全的实现
package com.jsxs.Test;import java.math.BigDecimal;/*** @Author Jsxs* @Date 2023/10/11 19:30* @PackageName:com.jsxs.Test* @ClassName: DecimalAccountUnsafe* @Description: TODO* @Version 1.0*/
public class DecimalAccountUnsafe implements DecimalAccount {BigDecimal balance;public DecimalAccountUnsafe(BigDecimal balance) {this.balance = balance;}@Overridepublic BigDecimal getBalance() {return balance;}@Overridepublic void withdraw(BigDecimal amount) {BigDecimal balance = this.getBalance();this.balance = balance.subtract(amount);}public static void main(String[] args) {DecimalAccount.demo(new DecimalAccountUnsafe(new BigDecimal("10000")));}
}
(2).安全实现_使用锁
package com.jsxs.Test;import java.math.BigDecimal;/*** @Author Jsxs* @Date 2023/10/11 20:09* @PackageName:com.jsxs.Test* @ClassName: DecimalAccountSafeLock* @Description: TODO* @Version 1.0*/
public class DecimalAccountSafeLock implements DecimalAccount{private final Object lock= new Object();BigDecimal balance;public DecimalAccountSafeLock(BigDecimal balance) {this.balance = balance;}@Overridepublic BigDecimal getBalance() {return balance;}@Overridepublic void withdraw(BigDecimal amount) {synchronized (lock){BigDecimal balance = this.getBalance();// 旧余额 - 取款 = 新余额this.balance = balance.subtract(amount);}}public static void main(String[] args) {DecimalAccount.demo(new DecimalAccountSafeLock(new BigDecimal("10000")));}}
(3).安全实现_使用 CAS (AtomicReference)
package com.jsxs.Test;import java.math.BigDecimal;
import java.util.concurrent.atomic.AtomicReference;/*** @Author Jsxs* @Date 2023/10/11 19:38* @PackageName:com.jsxs.Test* @ClassName: DecimalAccountSafeCas* @Description: TODO* @Version 1.0*/
public class DecimalAccountSafeCas implements DecimalAccount {// 1.我们这里使用原子引用类型进行修饰 ⭐AtomicReference<BigDecimal> ref;public DecimalAccountSafeCas(BigDecimal balance) {// 2.创建我们的原子引用类 ⭐⭐ref = new AtomicReference<>(balance);}@Overridepublic BigDecimal getBalance() {// 3.调用get()返回最新值 ⭐⭐⭐return ref.get();}@Overridepublic void withdraw(BigDecimal amount) {// 4. CAS ⭐⭐⭐⭐while (true) {// 获取现在的余额 (旧值)BigDecimal prev = ref.get();// 返回的新余额(新值) = 现在的余额 - 取款金额BigDecimal next = prev.subtract(amount);if (ref.compareAndSet(prev, next)) { // 进行我们的CAS操作, 主存与新值进行比较,一样则false。break;}}}public static void main(String[] args) {DecimalAccount.demo(new DecimalAccountSafeCas(new BigDecimal("10000"))); // 构造金额为10000}
}
(4).ABA 问题
- 无线程冲突的时候
package com.jsxs.Test;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.atomic.AtomicReference;/*** @Author Jsxs* @Date 2023/10/11 20:18* @PackageName:com.jsxs.Test* @ClassName: Test10* @Description: TODO* @Version 1.0*/
@Slf4j(topic = "c.test10")
public class Test10 {// 1. 设置原子引用类型static AtomicReference<String> ref = new AtomicReference<>("A");public static void main(String[] args) throws InterruptedException {log.debug("main start...");// 获取值 AString prev = ref.get();Thread.sleep(1000);// 尝试改为 Clog.debug("change A->C {}", ref.compareAndSet(prev, "C"));}
}
- ABA问题的展现
A->B->A->C
package com.jsxs.Test;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.atomic.AtomicReference;/*** @Author Jsxs* @Date 2023/10/11 20:18* @PackageName:com.jsxs.Test* @ClassName: Test10* @Description: TODO* @Version 1.0*/
@Slf4j(topic = "c.test10")
public class Test10 {// 1. 设置原子引用类型static AtomicReference<String> ref = new AtomicReference<>("A");public static void main(String[] args) throws InterruptedException {log.debug("main start...");// 获取值 A// 这个共享变量被它线程修改过?String prev = ref.get();other();Thread.sleep(1000);// 尝试改为 Clog.debug("change A->C {}", ref.compareAndSet(prev, "A"));}private static void other() throws InterruptedException {new Thread(() -> {log.debug("change A->B {}", ref.compareAndSet(ref.get(), "B"));}, "t1").start();Thread.sleep(500);new Thread(() -> {log.debug("change B->A {}", ref.compareAndSet(ref.get(), "A"));}, "t2").start();}
}
ABA问题成立!!!!
- 估计将A的地址进行修改之后
package com.jsxs.Test;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.atomic.AtomicReference;/*** @Author Jsxs* @Date 2023/10/11 20:18* @PackageName:com.jsxs.Test* @ClassName: Test10* @Description: TODO* @Version 1.0*/
@Slf4j(topic = "c.test10")
public class Test10 {// 1. 设置原子引用类型static AtomicReference<String> ref = new AtomicReference<>("A");public static void main(String[] args) throws InterruptedException {log.debug("main start...");// 获取值 A// 这个共享变量被它线程修改过?String prev = ref.get();other();Thread.sleep(1000);// 尝试改为 Clog.debug("change A->C {}", ref.compareAndSet(prev, "A"));}private static void other() throws InterruptedException {new Thread(() -> {log.debug("change A->B {}", ref.compareAndSet(ref.get(), "B"));}, "t1").start();Thread.sleep(500);new Thread(() -> {log.debug("change B->A {}", ref.compareAndSet(ref.get(), new String("A")));}, "t2").start();}
}
我们发现ABA问题将不成立!!!!
主线程仅能判断出共享变量的值与最初值 A 是否相同,不能感知到这种从 A 改为 B 又 改回 A 的情况,如果主线程希望:
(5).ABA问题解决1_AtomicStampedReference
只要有其它线程【动过了】共享变量,那么自己的 cas 就算失败,这时,仅比较值是不够的,需要再加一个版本号。
package com.jsxs.Test;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;/*** @Author Jsxs* @Date 2023/10/11 20:18* @PackageName:com.jsxs.Test* @ClassName: Test10* @Description: TODO* @Version 1.0*/
@Slf4j(topic = "c.test10")
public class Test10 {// 1. 设置原子版本引用类型static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);public static void main(String[] args) throws InterruptedException {log.debug("main start...");// 获取值 AString prev = ref.getReference();// 获取版本号 (提前获取⭐在其他线程进入前)int stamp = ref.getStamp();log.debug("版本 {}", stamp);// 如果中间有其它线程干扰,发生了 ABA 现象other();Thread.sleep(1000);// 尝试改为 Clog.debug("change A->C {}", ref.compareAndSet(prev, "C", stamp, stamp + 1)); // 判断stamp当前版本号,与主存中的版本号是否一致}private static void other() throws InterruptedException {new Thread(() -> {log.debug("change A->B {}", ref.compareAndSet(ref.getReference(), "B",ref.getStamp(), ref.getStamp() + 1));log.debug("更新版本为 {}", ref.getStamp());}, "t1").start();Thread.sleep(500);new Thread(() -> {log.debug("change B->A {}", ref.compareAndSet(ref.getReference(), "A",ref.getStamp(), ref.getStamp() + 1));log.debug("更新版本为 {}", ref.getStamp());}, "t2").start();}
}
AtomicStampedReference 可以给原子引用加上版本号,追踪原子引用整个的变化过程,如: A -> B -> A ->C ,通过AtomicStampedReference,我们可以知道,引用变量中途被更改了几次。
(6).ABA问题解决2_AtomicMarkableReference
但是有时候,并不关心引用变量更改了几次,只是单纯的关心是否更改过,所以就有了AtomicMarkableReference.
package com.jsxs.Test;/*** @Author Jsxs* @Date 2023/10/11 21:23* @PackageName:com.jsxs.Test* @ClassName: GarbageBag* @Description: TODO* @Version 1.0*/
public class GarbageBag {String desc;public GarbageBag(String desc) {this.desc = desc;}public void setDesc(String desc) {this.desc = desc;}@Overridepublic String toString() {return super.toString() + " " + desc;}
}
package com.jsxs.Test;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.atomic.AtomicMarkableReference;/*** @Author Jsxs* @Date 2023/10/11 21:24* @PackageName:com.jsxs.Test* @ClassName: TestABAAtomicMarkableReference* @Description: TODO* @Version 1.0*/
@Slf4j(topic = "c.test007")
public class TestABAAtomicMarkableReference {public static void main(String[] args) throws InterruptedException {GarbageBag bag = new GarbageBag("装满了垃圾");// 参数2 mark 可以看作一个标记,true表示垃圾袋满了 ⭐AtomicMarkableReference<GarbageBag> ref = new AtomicMarkableReference<>(bag, true);log.debug("主线程 start...");// 1.获取余额GarbageBag prev = ref.getReference();log.debug(prev.toString());// 2.保洁阿姨打扫卫生...new Thread(() -> {log.debug("打扫卫生的线程 start...");bag.setDesc("空垃圾袋");// 3. 保洁阿姨对判断是否是空垃圾袋 (也就是新的和主存是否一致)while (!ref.compareAndSet(bag, bag, true, false)) {}log.debug(bag.toString());}).start();Thread.sleep(1000);log.debug("主线程想换一只新垃圾袋?");boolean success = ref.compareAndSet(prev, new GarbageBag("空垃圾袋"), true, false);log.debug("换了么?" + success);log.debug(ref.getReference().toString());}
}
依然是同一个垃圾袋...
5.原子数组
- AtomicIntegerArray
- AtomicLongArray
- AtomicReferenceArray
原子数组主要保护的是我们数组中的元素!!!
(1).不安全的实现
package com.jsxs.Test;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;/*** @Author Jsxs* @Date 2023/10/13 19:15* @PackageName:com.jsxs.Test* @ClassName: Test11* @Description: TODO* @Version 1.0*/
public class Test11 {public static void main(String[] args) {demo(()->new int[10], // ⭐非原子数组的创建(array)->array.length,(array,index)->array[index]++,(array)-> System.out.println(Arrays.toString(array)));}/*** 参数1,提供数组、可以是线程不安全数组或线程安全数组* 参数2,获取数组长度的方法* 参数3,自增方法,回传 array, index* 参数4,打印数组的方法*/
// supplier 提供者 无中生有 ()->结果
// function 函数 一个参数一个结果 (参数)->结果 , BiFunction (参数1,参数2)->结果
// consumer 消费者 一个参数没结果 (参数)->void, BiConsumer (参数1,参数2)->private static <T> void demo(Supplier<T> arraySupplier, // 1.没有参数一个结果Function<T, Integer> lengthFun, // 2. 一个参数一个结果BiConsumer<T, Integer> putConsumer, // 3.两个参数,一个结果的Consumer<T> printConsumer) { // 4.一个参数,但没有返回值 (用作打印)List<Thread> ts = new ArrayList<>();// 1.获取数组T array = arraySupplier.get();// 2.设置数组的长度int length = lengthFun.apply(array);for (int i = 0; i < length; i++) {// 每个线程对数组作 10000 次操作ts.add(new Thread(() -> {for (int j = 0; j < 10000; j++) {putConsumer.accept(array, j % length);}}));}ts.forEach(t -> t.start()); // 启动所有线程ts.forEach(t -> {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}}); // 等所有线程结束printConsumer.accept(array);}
}
(2).安全实现_ 使用CAS
package com.jsxs.Test;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicIntegerArray;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;/*** @Author Jsxs* @Date 2023/10/13 19:15* @PackageName:com.jsxs.Test* @ClassName: Test11* @Description: TODO* @Version 1.0*/
public class Test11 {public static void main(String[] args) {demo(()->new AtomicIntegerArray(10), // 1.创建数组 (array)->array.length(), // 2. 提供长度(array,index)->array.getAndIncrement(index), // 3.对原子数组中的元素(下标)进行 +1 的操作(array)-> System.out.println(array));}/*** 参数1,提供数组、可以是线程不安全数组或线程安全数组* 参数2,获取数组长度的方法* 参数3,自增方法,回传 array, index* 参数4,打印数组的方法*/
// supplier 提供者 无中生有 ()->结果
// function 函数 一个参数一个结果 (参数)->结果 , BiFunction (参数1,参数2)->结果
// consumer 消费者 一个参数没结果 (参数)->void, BiConsumer (参数1,参数2)->private static <T> void demo(Supplier<T> arraySupplier, // 1.没有参数一个结果Function<T, Integer> lengthFun, // 2. 一个参数一个结果BiConsumer<T, Integer> putConsumer, // 3.两个参数,一个结果的Consumer<T> printConsumer) { // 4.一个参数,但没有返回值 (用作打印)List<Thread> ts = new ArrayList<>();// 1.获取数组T array = arraySupplier.get();// 2.设置数组的长度int length = lengthFun.apply(array);for (int i = 0; i < length; i++) {// 每个线程对数组作 10000 次操作ts.add(new Thread(() -> {for (int j = 0; j < 10000; j++) {putConsumer.accept(array, j % length);}}));}ts.forEach(t -> t.start()); // 启动所有线程ts.forEach(t -> {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}}); // 等所有线程结束printConsumer.accept(array);}
}
6.字段更新器
- AtomicReferenceFieldUpdater // 域 字段
- AtomicIntegerFieldUpdater
- AtomicLongFieldUpdater
字段更新器保护的是对象的属性和成员变量!!!
利用字段更新器,可以针对对象的某个域(Field)进行原子操作,只能配合 volatile 修饰的字段使用,否则会出现异常。
(1).未使用voliatile修饰的时候会报异常
package com.jsxs.Test;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;/*** @Author Jsxs* @Date 2023/10/13 20:10* @PackageName:com.jsxs.Test* @ClassName: Test12* @Description: TODO* @Version 1.0*/
@Slf4j(topic = "c.test12")
public class Test12 {public static void main(String[] args) {Student student = new Student();// 1.第一个参数: 类名; 第二个参数: 参数类型; 第三个: 参数名AtomicReferenceFieldUpdater updater =AtomicReferenceFieldUpdater.newUpdater(Student.class,String.class,"name");// 1.第一个参数: 对象名; 第二个参数:属性的原始值; 第三个参数: 修改成什么值updater.compareAndSet(student,null,"张三");}
}class Student {String name; // 我们这里没有使用 voliate@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +'}';}
}
(2).使用voliatile修饰后
为什么要添加voliatile进行修饰呢? 为了保证我们保护的成员变量的可见性!!!
package com.jsxs.Test;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;/*** @Author Jsxs* @Date 2023/10/13 20:10* @PackageName:com.jsxs.Test* @ClassName: Test12* @Description: TODO* @Version 1.0*/
@Slf4j(topic = "c.test12")
public class Test12 {public static void main(String[] args) {Student student = new Student();// 1.第一个参数: 类名; 第二个参数: 参数类型; 第三个: 参数名AtomicReferenceFieldUpdater updater =AtomicReferenceFieldUpdater.newUpdater(Student.class,String.class,"name");// 1.第一个参数: 对象名; 第二个参数:属性的原始值; 第三个参数: 修改成什么值updater.compareAndSet(student,null,"张三");System.out.println(student.toString());}
}class Student {volatile String name;@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +'}';}
}
(3).存在问题ABA问题
package com.jsxs.Test;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;/*** @Author Jsxs* @Date 2023/10/13 20:10* @PackageName:com.jsxs.Test* @ClassName: Test12* @Description: TODO* @Version 1.0*/
@Slf4j(topic = "c.test12")
public class Test12 {public static void main(String[] args) {Student student = new Student();// 1.第一个参数: 类名; 第二个参数: 参数类型; 第三个: 参数名 ⭐AtomicReferenceFieldUpdater updater =AtomicReferenceFieldUpdater.newUpdater(Student.class,String.class,"name");// 2.第一个参数: 对象名; 第二个参数:属性的原始值; 第三个参数: 修改成什么值 ⭐⭐updater.compareAndSet(student,null,"张三");System.out.println(student.toString());// 3.原始值指的正确,那么会成功。⭐⭐⭐updater.compareAndSet(student,"张三","张四");System.out.println(student.toString());// 4.原始值指的更改过的,那么会失败。 底层原理还是CAS进行比较的 ⭐⭐⭐⭐updater.compareAndSet(student,"张三","张五");System.out.println(student.toString());}
}class Student {volatile String name;@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +'}';}
}
7.原子累加器
累加器性能比较
(1).运用累加器类性能更高
package com.jsxs.Test;import lombok.extern.slf4j.Slf4j;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.Consumer;
import java.util.function.Supplier;/*** @Author Jsxs* @Date 2023/10/13 20:33* @PackageName:com.jsxs.Test* @ClassName: Test13* @Description: TODO* @Version 1.0*/
@Slf4j(topic = "c.test13")
public class Test13 {public static void main(String[] args) {demo(()->new AtomicLong(0), // 1.设置我们的原子长整型(adder)->adder.getAndIncrement() //2.进行自增的操作);System.out.println("======上面是原子长整型类,下面是长整型累加器=======");demo(()->new LongAdder(),(adder)->adder.increment());}/**** @param adderSupplier : ()->结果 ,提供累加器对象* @param action : (参数)-> 无返回结果, 提供累加操作* @param <T>*/private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action) {T adder = adderSupplier.get();long start = System.nanoTime();List<Thread> ts = new ArrayList<>();// 4 个线程,每人累加 50 万for (int i = 0; i < 40; i++) {ts.add(new Thread(() -> {for (int j = 0; j < 500000; j++) {action.accept(adder);}}));}ts.forEach(t -> t.start());ts.forEach(t -> {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}});long end = System.nanoTime();System.out.println(adder + " cost:" + (end - start) / 1000_000);}
}
(2).为什么性能高呢?
性能提升的原因很简单,就是在有竞争时,设置多个累加单元,Therad-0 累加 Cell[0],而 Thread-1 累加Cell[1]… 最后将结果汇总。这样它们在累加时操作的不同的 Cell 变量,因此减少了 CAS 重试失败,从而提高性能。相当于不共享一个累加单元,每一个线程都有一个自己的累加单元!!!
(4).源码之 LongAdder
LongAdder 是并发大师 @author Doug Lea (大哥李)的作品,设计的非常精巧
LongAdder 类有几个关键域
// 累加单元数组, 懒惰初始化transient volatile Cell[] cells;// 基础值, 如果没有竞争, 则用 cas 累加这个域transient volatile long base;// 在 cells 创建或扩容时, 置为 1, 表示加锁transient volatile int cellsBusy;
(5).cas 锁源码
CAS锁的源码实践,但是实际开发中我们不用用下面这样的代码进行开发
package com.jsxs.Test;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.atomic.AtomicInteger;/*** @Author Jsxs* @Date 2023/10/15 11:45* @PackageName:com.jsxs.Test* @ClassName: LockCas* @Description: TODO* @Version 1.0*/
@Slf4j(topic = "c.test")
public class LockCas {// 0的时候表示没有加锁,1的时候表示已经加锁private AtomicInteger state = new AtomicInteger(0);public void lock() {while (true) {if (state.compareAndSet(0, 1)) {break;}}}public void unlock() {log.debug("unlock...");state.set(0);}public static void main(String[] args) {LockCas lock = new LockCas();// 1.线程1执行完毕解锁之后,线程2才能继续执行new Thread(() -> {log.debug("begin...");lock.lock();try {log.debug("lock...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}} finally {lock.unlock();}}).start();new Thread(() -> {log.debug("begin...");lock.lock();try {log.debug("lock...");} finally {lock.unlock();}}).start();}
}
(6).原理之伪共享
其中 Cell 即为累加单元
@sun.misc.Contended //⭐注解: 防止缓存行伪共享
static final class Cell {volatile long value;Cell(long x) {value = x;}// 最重要的方法, 用来 cas 方式进行累加, prev 表示旧值, next 表示新值final boolean cas(long prev, long next) {return UNSAFE.compareAndSwapLong(this, valueOffset, prev, next);}// 省略不重要代码
}
得从缓存说起,缓存与内存的速度比较
为什么设置那么多的缓存呢?
因为 CPU 与 内存 的速度差异很大,需要靠预读数据至缓存来提升效率。
而缓存以缓存行为单位,每个缓存行对应着一块内存,一般是 64 byte(8 个 long)
缓存的加入会造成数据副本的产生,即同一份数据会缓存在不同核心的缓存行中。 也就是相当于取两份。
CPU 要保证数据的一致性,如果某个 CPU 核心更改了数据,其它 CPU 核心对应的整个缓存行必须失效。
因为 Cell 是数组形式,在内存中是连续存储的,一个 Cell 为 24 字节(16 字节的对象头和 8 字节的 value),因此缓存行可以存下 2 个的 Cell 对象。这样问题来了:
- Core-0 要修改 Cell[0]
- Core-1 要修改 Cell[1]
无论谁修改成功,都会导致对方 Core 的缓存行失效,比如 Core-0 中 Cell[0]=6000, Cell[1]=8000 要累加Cell[0]=6001, Cell[1]=8000 ,这时会让 Core-1 的缓存行失效
@sun.misc.Contended 用来解决这个问题,它的原理是在使用此注解的对象或字段的前后各增加 128 字节大小的padding,从而让 CPU 将对象预读至缓存时占用不同的缓存行,这样,不会造成对方缓存行的失效。
(7).add源码
public void add(long x) {// as 为累加单元数组// b 为基础值// x 为累加值Cell[] as;long b, v;int m;Cell a;// 进入 if 的两个条件// 1. as 有值, 表示已经发生过竞争, 进入 if// 2. cas 给 base 累加时失败了, 表示 base 发生了竞争, 进入 ifif ((as = cells) != null || !casBase(b = base, b + x)) {// uncontended 表示 cell 没有竞争boolean uncontended = true;if (// as 还没有创建as == null || (m = as.length - 1) < 0 ||// 当前线程对应的 cell 还没有(a = as[getProbe() & m]) == null ||// cas 给当前线程的 cell 累加失败 uncontended=false ( a 为当前线程的 cell )!(uncontended = a.cas(v = a.value, v + x))) {// 进入 cell 数组创建、cell 创建的流程longAccumulate(x, null, uncontended);}}}
8.UnSafe
(1).概述
Unsafe 对象提供了非常底层的,操作内存、线程的方法,Unsafe 对象不能直接调用,只能通过反射获得。
(2).获取UnSafe实列
package com.jsxs.Test;import sun.misc.Unsafe;import java.lang.reflect.Field;/*** @Author Jsxs* @Date 2023/10/18 21:00* @PackageName:com.jsxs.Test* @ClassName: Test14* @Description: TODO* @Version 1.0*/
public class Test14 {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {// 1.通过反射指定属性名获取具体属性Field unsafe = Unsafe.class.getDeclaredField("theUnsafe");// 2.因为是私有的成员变量,所以我们需要先设置Accessibleunsafe.setAccessible(true);// 3.返回我们的真正的UnSafe对象Unsafe o = (Unsafe)unsafe.get(null);System.out.println(o);}
}
(3).UnSafe的Case操作
package com.jsxs.Test;import lombok.Data;
import sun.misc.Unsafe;import java.lang.reflect.Field;/*** @Author Jsxs* @Date 2023/10/18 21:00* @PackageName:com.jsxs.Test* @ClassName: Test14* @Description: TODO* @Version 1.0*/
public class Test14 {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {// 1.通过反射指定属性名获取具体属性Field o = Unsafe.class.getDeclaredField("theUnsafe");// 2.因为是私有的成员变量,所以我们需要先设置Accessibleo.setAccessible(true);// 3.返回我们的真正的UnSafe对象Unsafe unsafe = (Unsafe)o.get(null);System.out.println(o);// 1.获取欲的偏移地址 : 获取Student1中id属性的偏移地址long idOffset = unsafe.objectFieldOffset(Student1.class.getDeclaredField("id"));long nameOffset = unsafe.objectFieldOffset(Student1.class.getDeclaredField("name"));Student1 student1 = new Student1();// 2.执行 cas 操作unsafe.compareAndSwapInt(student1,idOffset,0,1); // 第一个: 对象, 第二个: id的偏移地址,第三个: 旧值,第四个: 新值unsafe.compareAndSwapObject(student1,nameOffset,null,"张三"); // 第一个: 对象, 第二个: id的偏移地址,第三个: 旧值,第四个: 新值// 3.查看我们是否成功!! System.out.println(student1.id+" "+ student1.name);}
}@Data
class Student1 {volatile int id;volatile String name;
}
(4).模拟原子整数
使用自定义的 AtomicData 实现之前线程安全的原子整数 Account 实现。
1.Account接口
package com.jsxs.Test;import java.util.ArrayList;
import java.util.List;/*** @Author Jsxs* @Date 2023/10/11 14:58* @PackageName:com.jsxs.Test* @ClassName: Account* @Description: TODO* @Version 1.0*/
public interface Account {// 获取余额Integer getBalance();// 取款void withdraw(Integer amount);/*** 方法内会启动 1000 个线程,每个线程做 -10 元 的操作* 如果初始余额为 10000 那么正确的结果应当是 0*/static void demo(Account account) {List<Thread> ts = new ArrayList<>();long start = System.nanoTime();for (int i = 0; i < 1000; i++) {ts.add(new Thread(() -> {// 每次取10块钱account.withdraw(10);}));}ts.forEach(Thread::start);ts.forEach(t -> {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}});long end = System.nanoTime();System.out.println(account.getBalance()+ " cost: " + (end - start) / 1000_000 + " ms");}
}
2.实现
package com.jsxs.Test;import com.jsxs.utils.UnSafeUtils;
import sun.misc.Unsafe;import java.lang.reflect.Field;/*** @Author Jsxs* @Date 2023/10/18 21:32* @PackageName:com.jsxs.Test* @ClassName: Test15* @Description: TODO* @Version 1.0*/
public class Test15 {public static void main(String[] args) {Account.demo(new MyAtomicInteger(10000));}
}class MyAtomicInteger implements Account{volatile int value;static final long valueOffset;static Unsafe unsafe;static {try {// 1.通过反射机制进行获取UnSafe ⭐Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");theUnsafe.setAccessible(true);unsafe = (Unsafe) theUnsafe.get(null);// 2.获取我们偏移域valueOffset=unsafe.objectFieldOffset(MyAtomicInteger.class.getDeclaredField("value"));} catch (NoSuchFieldException | IllegalAccessException e) {throw new Error(e);}}public int getValue() {return value;}public void decrement(int amount){while (true){// 1.旧值int prev = this.value;// 2.新值int next=prev-amount;// 3.利用 UnSafe 进行设置 ⭐⭐if (unsafe.compareAndSwapInt(this,valueOffset,prev,next)){break;}}}@Overridepublic Integer getBalance() {return getValue();}public MyAtomicInteger(int value) {this.value = value;}@Overridepublic void withdraw(Integer amount) {decrement(amount);}}
测试成功
(七)、共享模型之不可变
- 不可变类的使用
- 不可变类设计
- 无状态类设计
1.日期转换的问题
(1).问题提出
下面的代码在运行时,由于 SimpleDateFormat 不是线程安全的。
package com.jsxs.Test;import lombok.extern.slf4j.Slf4j;import java.text.SimpleDateFormat;/*** @Author Jsxs* @Date 2023/10/19 20:17* @PackageName:com.jsxs.Test* @ClassName: Test16* @Description: TODO* @Version 1.0*/@Slf4j(topic = "test16")
public class Test16 {public static void main(String[] args) {// 1.进行我们的序列化操作SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");// 2.开启十个线程进行解析for (int i = 0; i < 10; i++) {new Thread(() -> {try {log.debug("{}", sdf.parse("1951-04-21"));} catch (Exception e) {log.error("{}", e);}}).start();}}
}
有很大几率出现 java.lang.NumberFormatException (数字类型错误
) 或者出现不正确的日期解析结果,例如:
(2).思路 - 同步锁
这样虽能解决问题,但带来的是性能上的损失,并不算很好:
package com.jsxs.Test;import lombok.extern.slf4j.Slf4j;import java.text.SimpleDateFormat;/*** @Author Jsxs* @Date 2023/10/19 20:17* @PackageName:com.jsxs.Test* @ClassName: Test16* @Description: TODO* @Version 1.0*/@Slf4j(topic = "c.test16")
public class Test16 {public static void main(String[] args) {// 1.进行我们的序列化操作SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");// 2.开启十个线程进行解析for (int i = 0; i < 10; i++) {new Thread(() -> {synchronized (sdf) { // ⭐try {log.debug("{}", sdf.parse("1951-04-21"));} catch (Exception e) {log.error("{}", e);}}}).start();}}
}
(3).思路 - 不可变
如果一个对象在不能够修改其内部状态(属性),那么它就是线程安全的,因为不存在并发修改啊
!这样的对象在Java 中有很多,例如在 Java 8 后,提供了一个新的日期格式化类:
package com.jsxs.Test;import lombok.extern.slf4j.Slf4j;import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;/*** @Author Jsxs* @Date 2023/10/19 20:17* @PackageName:com.jsxs.Test* @ClassName: Test16* @Description: TODO* @Version 1.0*/@Slf4j(topic = "c.test16")
public class Test16 {public static void main(String[] args) {// 1. 利用 线程安全的方法DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");for (int i = 0; i < 10; i++) {new Thread(() -> {LocalDate date = dtf.parse("2018-10-01", LocalDate::from);log.debug("{}", date);}).start();}}
}
2.不可变设计
另一个大家更为熟悉的 String 类也是不可变的,以它为例,说明一下不可变设计的要素
public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence {/*** The value is used for character storage.*/private final char value[];/*** Cache the hash code for the string*/private int hash; // Default to 0// ...}
(1).final 的使用
发现该类、类中所有属性都是 final 的
- 属性用 final 修饰保证了该属性是只读的,不能修改。
- 类用 final 修饰保证了该类中的方法不能被覆盖,防止子类无意间破坏不可变性。
(2).保护性拷贝
但有同学会说,使用字符串时,也有一些跟修改相关的方法啊,比如 substring 等,那么下面就看一看这些方法是如何实现的,就以 substring 为例:
深拷贝!!!: 实质上也就是创建了一个新的类。
public String substring(int beginIndex) {if (beginIndex < 0) {throw new StringIndexOutOfBoundsException(beginIndex);}int subLen = value.length - beginIndex;if (subLen < 0) {throw new StringIndexOutOfBoundsException(subLen);}return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);}
发现其内部是调用 String 的构造方法创建了一个新字符串,再进入这个构造看看,是否对 final char[] value 做出了修改:
public String(char value[], int offset, int count) {if (offset < 0) {throw new StringIndexOutOfBoundsException(offset);}if (count <= 0) {if (count < 0) {throw new StringIndexOutOfBoundsException(count);}if (offset <= value.length) {this.value = "".value;return;}}if (offset > value.length - count) {throw new StringIndexOutOfBoundsException(offset + count);}this.value = Arrays.copyOfRange(value, offset, offset + count);}
结果发现也没有,构造新字符串对象时,会生成新的 char[] value,对内容进行复制 。这种通过创建副本对象来避免共享的手段称之为【保护性拷贝(defensive copy)】。
3.享元模式
(1). 简介
定义 英文名称:Flyweight pattern
. 当需要重用数量有限的同一类对象时。
(2).体现
- 包装类
在JDK中 Boolean
,Byte
,Short
,Integer
,Long
,Character
等包装类提供了 valueOf 方法,例如 Long 的 valueOf
会缓存 -128~127 之间的 Long 对象,在这个范围之间会重用对象,大于这个范围,才会新建 Long 对象:
public static Long valueOf(long l) {final int offset = 128;if (l >= -128 && l <= 127) { // will cachereturn LongCache.cache[(int) l + offset];}return new Long(l);}
- Byte, Short, Long 缓存的范围都是 -128~127
- Character 缓存的范围是 0~127
- Integer的默认范围是 -128~127
- 最小值不能变
- 但最大值可以通过调整虚拟机参数
-Djava.lang.Integer.IntegerCache.high
来改变
- Boolean 缓存了 TRUE 和 FALSE
(3).DIY
例如:一个线上商城应用,QPS 达到数千,如果每次都重新创建和关闭数据库连接,性能会受到极大影响。 这时预先创建好一批连接,放入连接池。一次请求到达后,从连接池获取连接,使用完毕后再还回连接池,这样既节约了连接的创建和关闭时间,也实现了连接的重用,不至于让庞大的连接数压垮数据库。
class Pool {// 1. 连接池大小private final int poolSize;// 2. 连接对象数组private Connection[] connections;// 3. 连接状态数组 0 表示空闲, 1 表示繁忙private AtomicIntegerArray states;// 4. 构造方法初始化public Pool(int poolSize) {this.poolSize = poolSize;this.connections = new Connection[poolSize];this.states = new AtomicIntegerArray(new int[poolSize]);for (int i = 0; i < poolSize; i++) {connections[i] = new MockConnection("连接" + (i + 1));}}// 5. 借连接public Connection borrow() {while (true) {for (int i = 0; i < poolSize; i++) {// 获取空闲连接if (states.get(i) == 0) {if (states.compareAndSet(i, 0, 1)) {log.debug("borrow {}", connections[i]);return connections[i];}}}// 如果没有空闲连接,当前线程进入等待synchronized (this) {try {log.debug("wait...");this.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}// 6. 归还连接public void free(Connection conn) {for (int i = 0; i < poolSize; i++) {if (connections[i] == conn) {states.set(i, 0);synchronized (this) {log.debug("free {}", conn);this.notifyAll();}break;}}}}class MockConnection implements Connection {// 实现略
}
4.finnal原理
(1).设置 final 变量的原理
理解了 volatile 原理,再对比 final 的实现就比较简单了
public class TestFinal {final int a = 20;
}
字节码
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: bipush 20
7: putfield #2 // Field a:I<-- 写屏障
10: return
发现 final 变量的赋值也会通过 putfield 指令来完成,同样在这条指令之后也会加入写屏障,保证在其它线程读到它的值时不会出现为 0 的情况。 因为finnal不能进行修改的特点。
(2).获取final 变量的原理
Monitor 原理
5.无状态
在 web 阶段学习时,设计 Controller 时为了保证其线程安全,都会有这样的建议,不要为 Controller 设置成员变量,这种没有任何成员变量的类是线程安全的。
因为成员变量保存的数据也可以称为状态信息,因此没有成员变量就称之为【无状态】
相关文章:

138.【JUC并发编程- 03】
JUC并发编程- 03 (六)、共享模型之无锁1.问题提出(1).为什么不安全?(2).安全实现_使用锁(3).安全实现_使用CAS 2.CAS与volatile(1).CAS_原理介绍(2).CAS_Debug分析(3).volatile(4).为什么无锁效率高(5).CAS的特点 3.原子整形(1).原子整数类型_ 自增自减(2).原子整数类型_乘除模…...

React Hooks批量更新问题
React 版本17.0.2 import React, { useState } from react;const Demo () > {const [count, setCount] useState(0);const [count1, setCount1] useState(0);const [count2, setCount2] useState(0);console.log(Demo);const add async () > {await 10;setCount(c…...

竞赛 深度学习YOLO抽烟行为检测 - python opencv
文章目录 1 前言1 课题背景2 实现效果3 Yolov5算法3.1 简介3.2 相关技术 4 数据集处理及实验5 部分核心代码6 最后 1 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 基于深度学习YOLO抽烟行为检测 该项目较为新颖,适合作为竞赛课…...

cAdvisor监控Docker容器做性能测试
cAdvisor监控Docker容器做性能测试 缘起 当前有个服务做技术选型,服务要求比较高的性能,要做性能测试。部署的环境是容器话部署,但申请新的容器流程较长,于是我打算在流程走完之前简单评估下性能,来确定技术选型是否…...

记一次EDU证书站
如果文章对你有帮助,欢迎关注、点赞、收藏一键三连支持以下哦! 想要一起交流学习的小伙伴可以加zkaq222(备注CSDN,不备注通不过哦)进入学习,共同学习进步 目录 目录 1.前言: 2.信息搜集 3.漏…...

React高频面试题100+题,这一篇就够了!
1 - 5 题详解传送门>>>>>>>>>>>> 1. 如何在代码中判断一个 React 组件是 class component 还是 function component? 2. useRef / ref / forwardsRef 的区别是什么? 3. useRef和useState区别? 4. useEffect 的…...

mysql MVC jsp实现表分页
mysql是轻量级数据库 在三层架构中实现简单的分页 在数据库sql编程中需要编写sql语句 SELECT * FROM sys.student limit 5,5; limit x,y x是开始节点,y是开始节点后的需要显示的长度。 在jdbc编程中需要给出x和y 一般是页数*页码,显示的长度。 代…...

【微信小程序】数字化会议OA系统之首页搭建(附源码)
🎉🎉欢迎来到我的CSDN主页!🎉🎉 🏅我是Java方文山,一个在CSDN分享笔记的博主。📚📚 🌟推荐给大家我的专栏《微信小程序开发实战》。🎯Ἲ…...

Leetcode——二维数组及滚动数组练习
118. 杨辉三角 class Solution { public:vector<vector<int>> generate(int numRows) {// 定义二维数组vector<vector<int>> num(numRows);for(int i0;i<numRows;i){//这里是给内层vector定义大小。默认是0,这里n是个数,不是值num[i].re…...

钢水包升降翻转液压系统比例阀放大器
钢水包升降翻转液压系统是一种用于控制钢水包升降和翻转的液压系统。该系统主要由液压泵、液压缸、控制阀和一些辅助元件组成。 钢水包升降翻转液压系统的液压泵将油从油箱中抽出,将油压力提高到一定值,然后通过控制阀将油分配到液压缸中。液压缸内的活…...

通达OA-通用版-V12,流程及表单自定义好用的类
通达OA-通用版-V12,流程及表单自定义好用的类 V1.0 V1.0 日期:2023年10月19日 新建类文件,将下面代码复制到文件中即可。 功能说明: 1、根据flow_id,可以获取流程表单相关的表名、及字段名。 2、根据run_id࿰…...

如何在vue中实现图片懒加载
1.什么是图片懒加载 概念当图片还未出现在浏览器的可视区,图片并不加载src所引入的图片资源。只有当图片出现在可视区时,才加载图片资源。 好处:当页面中需要许多图片进行展示。但是,在我们不可视区的图片我们可以进行图片懒加载…...

Mac 远程桌面软件
对于使用 Mac 计算机和笔记本电脑的企业来说,适用于 Mac 的远程桌面软件变得越来越重要,随着远程工作变得越来越普遍,IT 管理员和组织需要一种安全的方式来访问和修复问题、处理紧急任务以及监控远程工作站的状态,为了促进远距离协…...

EPLAN_005#宏边框、页宏、窗口宏/符号宏
一、宏边框 红边框不能用,变成了灰色 要在项目属性中更改位宏项目——才能使用宏边框功能 注意:创建宏边框时候要打开——显示隐藏元素 框选目标后,双击红边框的边——弹出红边框创建属性对话框——输入名称——更改变量ABC等 最后——自动…...

如何正确地使用ChatGPT(角色扮演+提示工程)
如何正确地使用ChatGPT 一、ChatGPT介绍二、准备工作2.1 获取ChatGPT环境2.2 确定使用ChatGPT的目标和需求 三、重要因素3.1 角色赋予3.2 提示工程 四、正确案例 一、ChatGPT介绍 可以查阅ChatGPT快速入门 二、准备工作 2.1 获取ChatGPT环境 国外的有OpenAI和微软NewBing等…...

MySQL服务安装与登录
(1)以管理员身份启动命令提示符: (2)定位到安装目录的bin目录下(根据自己的安装路径进行调整即可)。先输入“d:”,定位到d盘,输入cd空格文件路径(直接复制粘贴…...

论文阅读之《Kindling the Darkness: A Practical Low-light Image Enhancer》
目录 摘要 介绍 已有方法回顾 普通方法 基于亮度的方法 基于深度学习的方法 基于图像去噪的方法 提出的方法 2.1 Layer Decomposition Net 2.2 Reflectance Restoration Net 2.3 Illumination Adjustment Net 实验结果 总结 Kindling the Darkness: A Practical L…...

性能测试基础知识及性能指标
前言:最近公司接了个项目,领导开会突然来了句,让我出一份性能测试方案,后面性能测试工作交给我!我心里想之前面试没要求会这个啊(最少得加钱才能做吧~,没办法既然下达了指令,那就只能…...

ArcGIS笔记10_如何创建渔网?
本文目录 前言Step 1 确定渔网的精度单位Step 2 有底图时创建渔网的操作 前言 ArcGIS中的渔网是一个很好用的工具,它可以创建出规规整整的小格子,每个小格子都对应一个标注点,可以将原本散乱的数据规整化,如下图: Ste…...

Jmeter安装(快速入门)
大家好我是苏麟今天简单聊一下Jmeter . Jmeter Jmeter官网 : Apache JMeter - Download Apache JMeter 安装Jmeter Jmeter依赖于JDK,所以必须确保当前计算机上已经安装了JDK,并且配置了环境变量。 下载 : 官网下载 : 网盘自取 : 链接:…...

一个react前端项目中的配置文件作用解析
前端框架中用到的配置文件 .editorconfig, .gitignore, .npmrc, .prettierignore, .prettierrc, .umirc.local.ts, .umirc.ts, package-lock.json, package.json, README.md, tsconfig.json, typings.d.ts, webpack.config.js.editorconfig:用于定义代码编辑器的配…...

MAC 配置 Maven
Maven 是一个流行的 Java 项目管理和构建工具,它可以帮助我们管理项目依赖、构建和发布等过程。本文将指导您在 MAC 上配置 Maven 的详细步骤。 1、下载 Maven 首先,从 Maven 官方网站下载最新版本的 Maven 安装包。下载完成后,将其解压到您…...

vue3.0 + element plus upload图片 上传
直接上图吧: 记录一下,方便后续遇到啥问题...

Leetcode 18:四数之和
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复)。 解题思路:…...

word误删除的文件怎么恢复?恢复办法分享
在日常工作和学习中,我们常常会使用到Word来撰写文章、毕业论文、方案等。然而,我们可能会遇到Word误删文件的情况,令我们陷入恐慌,特别是这个文件很重要时。幸运的是,有办法找回。下面一起来看下word误删除的文件怎么…...

提高Qt开发软件运算性能提升
编译器minGW32,release版本,大部分操作在线程循环里面更容易体现出来 1、网上有说opencv像素处理使用直接获取Mat对象的像素块的数据指针,例如 for (int row 0; row < h; row) { uchar* uc_pixel image.data row * image.step; for (int col …...

WordPress SMTP邮件发送插件 Easy WP SMTP
Easy WP SMTP是一款 WordPress 邮件发送插件,WordPress 中经常用到邮件发送,包括新注册用户的邮件通知、找回密码通知、评论回复通知等。因为云服务器默认不启用 SMTP功能,所以需要安装 SMTP插件来解决这个问题。 SMTP 主机:smtp.…...

大咖云集,智慧碰撞|第 18 届 CLK 大会完整议程揭晓(内附报名通道)
自 2006 年以来,在国内 Linux 技术爱好者和行业公司的鼎力支持下,中国 Linux 内核开发者大会已走过 17个年头,是中国 Linux 内核领域最具影响力的峰会之一。今年的中国内核开发者大会依然秉承历届理念,以“自由、协作、创新”为理…...

springweb+vue前后端分离开发,集成部署
背景: 在自己做测试的时候,由于需要项目和项目的前端页面使用同样接口访问,所以需要将前端代码部署到后端项目下。前端采用vue,后端采用springboot。 首先时建立一个vue项目,这个可以参照网上的案例,创建方…...

美芯片禁令再次扩大,波及英伟达、AMD以及intel等科技公司 | 百能云芯
拜登政府17日宣布,计划停止英伟达(Nvidia)、超微半导体以及英特尔等科技公司设计的先进AI芯片输出中国大陆,英伟达(Nvidia)昨日股价重挫4.68%至每股439.38美元;天风国际证券分析师郭明錤表示&am…...