138.【JUC并发编程

编程入门 行业动态 更新时间:2024-10-25 20:24:55

138.【<a href=https://www.elefans.com/category/jswz/34/1748327.html style=JUC并发编程"/>

138.【JUC并发编程

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 (balancepareAndSet(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 (balancepareAndSet(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的特点

结合 CASvolatile 可以实现无锁并发适用于线程数少、多核 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 (refpareAndSet(prev, next)) {  // 进行我们的CAS操作,  主存与新值进行比较,一样则false。break;}}}public static void main(String[] args) {DecimalAccount.demo(new DecimalAccountSafeCas(new BigDecimal("10000")));  // 构造金额为10000}
}

(4).ABA 问题
  1. 无线程冲突的时候
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 {}", refpareAndSet(prev, "C"));}
}

  1. 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 {}", refpareAndSet(prev, "A"));}private static void other() throws InterruptedException {new Thread(() -> {log.debug("change A->B {}", refpareAndSet(ref.get(), "B"));}, "t1").start();Thread.sleep(500);new Thread(() -> {log.debug("change B->A {}", refpareAndSet(ref.get(), "A"));}, "t2").start();}
}

ABA问题成立!!!!

  1. 估计将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 {}", refpareAndSet(prev, "A"));}private static void other() throws InterruptedException {new Thread(() -> {log.debug("change A->B {}", refpareAndSet(ref.get(), "B"));}, "t1").start();Thread.sleep(500);new Thread(() -> {log.debug("change B->A {}", refpareAndSet(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 {}", refpareAndSet(prev, "C", stamp, stamp + 1));  // 判断stamp当前版本号,与主存中的版本号是否一致}private static void other() throws InterruptedException {new Thread(() -> {log.debug("change A->B {}", refpareAndSet(ref.getReference(), "B",ref.getStamp(), ref.getStamp() + 1));log.debug("更新版本为 {}", ref.getStamp());}, "t1").start();Thread.sleep(500);new Thread(() -> {log.debug("change B->A {}", refpareAndSet(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 (!refpareAndSet(bag, bag, true, false)) {}log.debug(bag.toString());}).start();Thread.sleep(1000);log.debug("主线程想换一只新垃圾袋?");boolean success = refpareAndSet(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.第一个参数: 对象名; 第二个参数:属性的原始值; 第三个参数: 修改成什么值updaterpareAndSet(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.第一个参数: 对象名; 第二个参数:属性的原始值; 第三个参数: 修改成什么值updaterpareAndSet(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.第一个参数: 对象名; 第二个参数:属性的原始值; 第三个参数: 修改成什么值 ⭐⭐updaterpareAndSet(student,null,"张三");System.out.println(student.toString());// 3.原始值指的正确,那么会成功。⭐⭐⭐updaterpareAndSet(student,"张三","张四");System.out.println(student.toString());// 4.原始值指的更改过的,那么会失败。 底层原理还是CAS进行比较的 ⭐⭐⭐⭐updaterpareAndSet(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 (statepareAndSet(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 UNSAFEpareAndSwapLong(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 操作unsafepareAndSwapInt(student1,idOffset,0,1);  // 第一个: 对象, 第二个: id的偏移地址,第三个: 旧值,第四个: 新值unsafepareAndSwapObject(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 (unsafepareAndSwapInt(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).体现
  1. 包装类

在JDK中 BooleanByteShortIntegerLongCharacter 等包装类提供了 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 (statespareAndSet(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并发编程

本文发布于:2023-12-06 16:59:48,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1668229.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:JUC

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!