6.共享模型之无锁 6.1 问题提出 (应用之互斥) 有如下需求,保证 account.withdraw 取款方法的线程安全
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package cn.itcast;import java.util.ArrayList;import java.util.List;interface Account { Integer getBalance () ; void withdraw (Integer amount) ; 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" ); } }
原有实现并不是线程安全的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class AccountUnsafe implements Account { private Integer balance; public AccountUnsafe (Integer balance) { this .balance = balance; } @Override public Integer getBalance () { return balance; } @Override public void withdraw (Integer amount) { balance -= amount; } }
执行测试代码
1 2 3 public static void main (String[] args) { Account.demo(new AccountUnsafe (10000 )); }
某次的执行结果
为什么不安全 withdraw
方法
1 2 3 public void withdraw (Integer amount) { balance -= amount; }
对应的字节码
1 2 3 4 5 6 7 8 9 ALOAD 0 ALOAD 0 GETFIELD cn/itcast/AccountUnsafe.balance : Ljava/lang/Integer; INVOKEVIRTUAL java/lang/Integer.intValue ()I ALOAD 1 INVOKEVIRTUAL java/lang/Integer.intValue ()I ISUB INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer; PUTFIELD cn/itcast/AccountUnsafe.balance : Ljava/lang/Integer;
多线程执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ALOAD 0 ALOAD 0 GETFIELD cn/itcast/AccountUnsafe.balance INVOKEVIRTUAL java/lang/Integer.intValue ALOAD 1 INVOKEVIRTUAL java/lang/Integer.intValue ISUB INVOKESTATIC java/lang/Integer.valueOf PUTFIELD cn/itcast/AccountUnsafe.balance ALOAD 0 ALOAD 0 GETFIELD cn/itcast/AccountUnsafe.balance INVOKEVIRTUAL java/lang/Integer.intValue ALOAD 1 INVOKEVIRTUAL java/lang/Integer.intValue ISUB INVOKESTATIC java/lang/Integer.valueOf PUTFIELD cn/itcast/AccountUnsafe.balance
原因:Integer虽然是不可变类,其方法是线程安全的,但是以上操作涉及到了多个方法的组合,等价于以下代码:
balance = new Integer(Integer.valueOf(balance) - amount);
前一个方法(valueOf)的结果决定后一个方法(构造方法),这种组合在多线程环境下线程不安全。
解决思路-锁 (悲观互斥)首先想到的是给 Account 对象加锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class AccountUnsafe implements Account { private Integer balance; public AccountUnsafe (Integer balance) { this .balance = balance; } @Override public synchronized Integer getBalance () { return balance; } @Override public synchronized void withdraw (Integer amount) { balance -= amount; } }
结果为
解决思路-无锁 (乐观重试)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class AccountSafe implements Account { private AtomicInteger balance; public AccountSafe (Integer balance) { this .balance = new AtomicInteger (balance); } @Override public Integer getBalance () { return balance.get(); } @Override public void withdraw (Integer amount) { while (true ) { int prev = balance.get(); int next = prev - amount; if (balance.compareAndSet(prev, next)) { break ; } } } }
执行测试代码
1 2 3 public static void main (String[] args) { Account.demo(new AccountSafe (10000 )); }
某次的执行结果
6.2 CAS(compareAndSet) 与 volatile 前面看到的 AtomicInteger 的解决方法,内部并没有用锁来保护共享变量的线程安全。那么它是如何实现的呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public void withdraw (Integer amount) { while (true ) { while (true ) { int prev = balance.get(); int next = prev - amount; if (balance.compareAndSet(prev, next)) { break ; } } } }
其中的关键是 compareAndSet,它的简称就是 CAS (也有 Compare And Swap 的说法),它必须是原子操作。
注意
其实 CAS 的底层是 lock cmpxchg 指令(X86 架构),在单核 CPU 和多核 CPU 下都能够保证【比较-交 换】的原子性。
在多核状态下,某个核执行到带 lock 的指令时,CPU 会让总线锁住,当这个核把此指令执行完毕,再 开启总线。这个过程中不会被线程的调度机制所打断,保证了多个线程对内存操作的准确性,是原子 的。
慢动作分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Slf4j public class SlowMotion { public static void main (String[] args) { AtomicInteger balance = new AtomicInteger (10000 ); int mainPrev = balance.get(); log.debug("try get {}" , mainPrev); new Thread (() -> { sleep(1000 ); int prev = balance.get(); balance.compareAndSet(prev, 9000 ); log.debug(balance.toString()); }, "t1" ).start(); sleep(2000 ); log.debug("try set 8000..." ); boolean isSuccess = balance.compareAndSet(mainPrev, 8000 ); log.debug("is success ? {}" , isSuccess); if (!isSuccess){ mainPrev = balance.get(); log.debug("try set 8000..." ); isSuccess = balance.compareAndSet(mainPrev, 8000 ); log.debug("is success ? {}" , isSuccess); } } private static void sleep (int millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { e.printStackTrace(); } } }
输出结果
1 2 3 4 5 6 2019-10-13 11:28:37.134 [main] try get 10000 2019-10-13 11:28:38.154 [t1] 9000 2019-10-13 11:28:39.154 [main] try set 8000... 2019-10-13 11:28:39.154 [main] is success ? false 2019-10-13 11:28:39.154 [main] try set 8000... 2019-10-13 11:28:39.154 [main] is success ? true
volatile 获取共享变量时,为了保证该变量的可见性,需要使用 volatile 修饰。
它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取 它的值,线程操作 volatile 变量都是直接操作主存。即一个线程对 volatile 变量的修改,对另一个线程可见。
注意
volatile 仅仅保证了共享变量的可见性,让其它线程能够看到最新值,但不能解决指令交错问题(不能保证原子性)
CAS 必须借助 volatile 才能读取到共享变量的最新值来实现【比较并交换】的效果。
为什么无锁效率高
无锁情况下,即使重试失败,线程始终在高速运行,没有停歇,类似于自旋。而 synchronized 会让线程在没有获得锁的时候,发生上下文切换,进入阻塞。线程的上下文切换是费时的,在重试次数不是太多时,无锁的效率高于有锁。 线程就好像高速跑道上的赛车,高速运行时,速度超快,一旦发生上下文切换,就好比赛车要减速、熄火, 等被唤醒又得重新打火、启动、加速... 恢复到高速运行,代价比较大 但无锁情况下,因为线程要保持运行,需要额外 CPU 的支持,CPU 在这里就好比高速跑道,没有额外的跑 道,线程想高速运行也无从谈起,虽然不会进入阻塞,但由于没有分到时间片,仍然会进入可运行状态,还 是会导致上下文切换。所以总的来说,当线程数小于等于cpu核心数时,使用无锁方案是很合适的,因为有足够多的cpu让线程运行。当线程数远多于cpu核心数时,无锁效率相比于有锁就没有太大优势,因为依旧会发生上下文切换。
CAS 的特点 结合 CAS 和 volatile 可以实现无锁并发,适用于线程数少、多核 CPU 的场景下。
CAS 是基于乐观锁的思想 :最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再 重试呗。synchronized 是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想 改,我改完了解开锁,你们才有机会。 CAS 体现的是无锁并发、无阻塞并发,请仔细体会这两句话的意思因为没有使用 synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一 但如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响 6.3 原子整数 J.U.C 并发包提供了:
AtomicBoolean AtomicInteger AtomicLong 以 AtomicInteger 为例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 AtomicInteger i = new AtomicInteger (0 ); System.out.println(i.getAndIncrement()); System.out.println(i.incrementAndGet()); System.out.println(i.decrementAndGet()); System.out.println(i.getAndDecrement()); System.out.println(i.getAndAdd(5 )); System.out.println(i.addAndGet(-5 )); System.out.println(i.getAndUpdate(p -> p - 2 )); System.out.println(i.updateAndGet(p -> p + 2 )); System.out.println(i.getAndAccumulate(10 , (p, x) -> p + x)); System.out.println(i.accumulateAndGet(-10 , (p, x) -> p + x));
说明:
6.4 原子引用 为什么需要原子引用类型?
AtomicReference AtomicMarkableReference AtomicStampedReference 实际开发的过程中我们使用的不一定是int、long等基本数据类型,也有可能时BigDecimal这样的类型,这时就需要用到原子引用作为容器。原子引用设置值使用的是unsafe.compareAndSwapObject()
方法。原子引用中表示数据的类型需要重写equals()
方法。
有如下方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public interface DecimalAccount { BigDecimal getBalance () ; void withdraw (BigDecimal amount) ; 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()); } }
试着提供不同的 DecimalAccount 实现,实现安全的取款操作
不安全实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class DecimalAccountUnsafe implements DecimalAccount { BigDecimal balance; public DecimalAccountUnsafe (BigDecimal balance) { this .balance = balance; } @Override public BigDecimal getBalance () { return balance; } @Override public void withdraw (BigDecimal amount) { BigDecimal balance = this .getBalance(); this .balance = balance.subtract(amount); } }
安全实现-使用锁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class DecimalAccountSafeLock implements DecimalAccount { private final Object lock = new Object (); BigDecimal balance; public DecimalAccountSafeLock (BigDecimal balance) { this .balance = balance; } @Override public BigDecimal getBalance () { return balance; } @Override public void withdraw (BigDecimal amount) { synchronized (lock) { BigDecimal balance = this .getBalance(); this .balance = balance.subtract(amount); } } }
安全实现-使用 CAS 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class DecimalAccountSafeCas implements DecimalAccount { AtomicReference<BigDecimal> ref; public DecimalAccountSafeCas (BigDecimal balance) { ref = new AtomicReference <>(balance); } @Override public BigDecimal getBalance () { return ref.get(); } @Override public void withdraw (BigDecimal amount) { while (true ) { BigDecimal prev = ref.get(); BigDecimal next = prev.subtract(amount); if (ref.compareAndSet(prev, next)) { break ; } } } }
测试代码
1 2 3 DecimalAccount.demo(new DecimalAccountUnsafe(new BigDecimal("10000" ))); DecimalAccount.demo(new DecimalAccountSafeLock(new BigDecimal("10000" ))); DecimalAccount.demo(new DecimalAccountSafeCas(new BigDecimal("10000" )));
运行结果
1 2 3 4310 cost: 425 ms 0 cost: 285 ms 0 cost: 274 ms
ABA 问题及解决 ABA 问题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 static AtomicReference<String> ref = new AtomicReference <>("A" );public static void main (String[] args) throws InterruptedException { log.debug("main start..." ); String prev = ref.get(); other(); sleep(1 ); log.debug("change A->C {}" , ref.compareAndSet(prev, "C" )); }private static void other () { new Thread (() -> { log.debug("change A->B {}" , ref.compareAndSet(ref.get(), "B" )); }, "t1" ).start(); sleep(0.5 ); new Thread (() -> { log.debug("change B->A {}" , ref.compareAndSet(ref.get(), "A" )); }, "t2" ).start(); }
输出
1 2 3 4 11:29:52.325 c.Test36 [main] - main start... 11:29:52.379 c.Test36 [t1] - change A->B true 11:29:52.879 c.Test36 [t2] - change B->A true 11:29:53.880 c.Test36 [main] - change A->C true
主线程仅能判断出共享变量的值与最初值 A 是否相同,不能感知到这种从 A 改为 B 又 改回 A 的情况,如果主线程 希望:
只要有其它线程【动过了】共享变量,那么自己的 cas 就算失败,这时,仅比较值是不够的,需要再加一个版本号
AtomicStampedReference
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 static AtomicStampedReference<String> ref = new AtomicStampedReference <>("A" , 0 );public static void main (String[] args) throws InterruptedException { log.debug("main start..." ); String prev = ref.getReference(); int stamp = ref.getStamp(); log.debug("版本 {}" , stamp); other(); sleep(1 ); log.debug("change A->C {}" , ref.compareAndSet(prev, "C" , stamp, stamp + 1 )); }private static void other () { new Thread (() -> { log.debug("change A->B {}" , ref.compareAndSet(ref.getReference(), "B" , ref.getStamp(), ref.getStamp() + 1 )); log.debug("更新版本为 {}" , ref.getStamp()); }, "t1" ).start(); sleep(0.5 ); new Thread (() -> { log.debug("change B->A {}" , ref.compareAndSet(ref.getReference(), "A" , ref.getStamp(), ref.getStamp() + 1 )); log.debug("更新版本为 {}" , ref.getStamp()); }, "t2" ).start(); }
输出为
1 2 3 4 5 6 7 15:41:34.891 c.Test36 [main] - main start... 15:41:34.894 c.Test36 [main] - 版本 0 15:41:34.956 c.Test36 [t1] - change A->B true 15:41:34.956 c.Test36 [t1] - 更新版本为 1 15:41:35.457 c.Test36 [t2] - change B->A true 15:41:35.457 c.Test36 [t2] - 更新版本为 2 15:41:36.457 c.Test36 [main] - change A->C false
AtomicStampedReference 可以给原子引用加上版本号,追踪原子引用整个的变化过程,如: A -> B -> A -> C
,通过AtomicStampedReference,我们可以知道,引用变量中途被更改了几次。
但是有时候,并不关心引用变量更改了几次,只是单纯的关心是否更改过,所以就有了 AtomicMarkableReference
AtomicMarkableReference
1 2 3 4 5 6 7 8 9 10 11 12 13 class GarbageBag { String desc; public GarbageBag (String desc) { this .desc = desc; } public void setDesc (String desc) { this .desc = desc; } @Override public String toString () { return super .toString() + " " + desc; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Slf4j public class TestABAAtomicMarkableReference { public static void main (String[] args) throws InterruptedException { GarbageBag bag = new GarbageBag ("装满了垃圾" ); AtomicMarkableReference<GarbageBag> ref = new AtomicMarkableReference <>(bag, true ); log.debug("主线程 start..." ); GarbageBag prev = ref.getReference(); log.debug(prev.toString()); new Thread (() -> { log.debug("打扫卫生的线程 start..." ); bag.setDesc("空垃圾袋" ); 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()); } }
输出
1 2 3 4 5 6 7 2019-10-13 15:30:09.264 [main] 主线程 start... 2019-10-13 15:30:09.270 [main] cn.itcast.GarbageBag@5f0fd5a0 装满了垃圾 2019-10-13 15:30:09.293 [Thread-1] 打扫卫生的线程 start... 2019-10-13 15:30:09.294 [Thread-1] cn.itcast.GarbageBag@5f0fd5a0 空垃圾袋 2019-10-13 15:30:10.294 [main] 主线程想换一只新垃圾袋? 2019-10-13 15:30:10.294 [main] 换了么?false 2019-10-13 15:30:10.294 [main] cn.itcast.GarbageBag@5f0fd5a0 空垃圾袋
可以注释掉打扫卫生线程代码,再观察输出
6.5 原子数组 AtomicIntegerArray AtomicLongArray AtomicReferenceArray 有如下方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 private static <T> void demo ( Supplier<T> arraySupplier, Function<T, Integer> lengthFun, BiConsumer<T, Integer> putConsumer, Consumer<T> printConsumer ) { List<Thread> ts = new ArrayList <>(); T array = arraySupplier.get(); int length = lengthFun.apply(array); for (int i = 0 ; i < length; i++) { 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); }
不安全的数组
1 2 3 4 5 6 demo( ()->new int [10 ], (array)->array.length, (array, index) -> array[index]++, array-> System.out.println(Arrays.toString(array)) );
结果
1 [9870, 9862, 9774, 9697, 9683, 9678, 9679, 9668, 9680, 9698]
安全的数组
1 2 3 4 5 6 demo( ()-> new AtomicIntegerArray (10 ), (array) -> array.length(), (array, index) -> array.getAndIncrement(index), array -> System.out.println(array) );
结果
1 [10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]
6.6 字段更新器 AtomicReferenceFieldUpdater // 域 字段 AtomicIntegerFieldUpdater AtomicLongFieldUpdater 利用字段更新器,可以针对对象的某个域(Field)进行原子操作,只能配合 volatile 修饰的字段使用,否则会出现 异常 1 Exception in thread "main" java.lang.IllegalArgumentException: Must be volatile type
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Test5 { private volatile int field; public static void main (String[] args) { AtomicIntegerFieldUpdater fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Test5.class, "field" ); Test5 test5 = new Test5 (); fieldUpdater.compareAndSet(test5, 0 , 10 ); System.out.println(test5.field); fieldUpdater.compareAndSet(test5, 10 , 20 ); System.out.println(test5.field); fieldUpdater.compareAndSet(test5, 10 , 30 ); System.out.println(test5.field); } }
输出
6.7 原子累加器 累加器性能比较 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private static <T> void demo (Supplier<T> adderSupplier, Consumer<T> action) { T adder = adderSupplier.get(); long start = System.nanoTime(); List<Thread> ts = new ArrayList <>(); 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 ); }
比较 AtomicLong 与 LongAdder
1 2 3 4 5 6 for (int i = 0 ; i < 5 ; i++) { demo(() -> new LongAdder (), adder -> adder.increment()); }for (int i = 0 ; i < 5 ; i++) { demo(() -> new AtomicLong (), adder -> adder.getAndIncrement()); }
输出
1 2 3 4 5 6 7 8 9 10 1000000 cost:43 1000000 cost:9 1000000 cost:7 1000000 cost:7 1000000 cost:7 1000000 cost:31 1000000 cost:27 1000000 cost:28 1000000 cost:24 1000000 cost:22
性能提升的原因很简单,就是在有竞争时,设置多个累加单元,Therad-0 累加 Cell[0],而 Thread-1 累加 Cell[1]... 最后将结果汇总。这样它们在累加时操作的不同的 Cell 变量,因此减少了 CAS 重试失败,从而提高性能。
* 原理之伪共享(CPU 缓存结构) CPU 缓存结构
查看 cpu 缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 ⚡ root@yihang01 ~ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian CPU(s): 1 On-line CPU(s) list: 0 Thread(s) per core: 1 Core(s) per socket: 1 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 142 Model name: Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz Stepping: 11 CPU MHz: 1992.002 BogoMIPS: 3984.00 Hypervisor vendor: VMware Virtualization type : full L1d cache: 32K L1i cache: 32K L2 cache: 256K L3 cache: 8192K NUMA node0 CPU(s): 0
速度比较
寄存器 1 cycle L1 3~4 cycle L2 10~20 cycle L3 40~45 cycle 内存 120~240 cycle
查看 cpu 缓存行
1 2 ⚡ root@yihang01 ~ cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size 64
cpu 拿到的内存地址格式是这样的
CPU 缓存读 读取数据流程如下
根据低位,计算在缓存中的索引 判断是否有效0 去内存读取新数据更新缓存行 1 再对比高位组标记是否一致一致,根据偏移量返回缓存数据 不一致,去内存读取新数据更新缓存行 CPU 缓存一致性 MESI 协议
E、S、M 状态的缓存行都可以满足 CPU 的读请求 E 状态的缓存行,有写请求,会将状态改为 M,这时并不触发向主存的写 E 状态的缓存行,必须监听该缓存行的读操作,如果有,要变为 S 状态
M 状态的缓存行,必须监听该缓存行的读操作,如果有,先将其它缓存(S 状态)中该缓存行变成 I 状态(即 6. 的流程),写入主存,自己变为 S 状态 S 状态的缓存行,有写请求,走 4. 的流程 S 状态的缓存行,必须监听该缓存行的失效操作,如果有,自己变为 I 状态 I 状态的缓存行,有读请求,必须从主存读取
✨✨内存屏障(读屏障和写屏障) Memory Barrier(Memory Fence)
可见性
写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中 而读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据 有序性
写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后 读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前
* 源码之 LongAdder LongAdder 是并发大师 @author Doug Lea (大哥李)的作品,设计的非常精巧
LongAdder 类有几个关键域
1 2 3 4 5 6 transient volatile Cell[] cells;transient volatile long base;transient volatile int cellsBusy;
其中 Cell 即为累加单元
1 2 3 4 5 6 7 8 9 10 11 12 @sun .misc.Contendedstatic final class Cell { volatile long value; Cell(long x) { value = x; } final boolean cas (long prev, long next) { return UNSAFE.compareAndSwapLong(this , valueOffset, prev, next); } }
@sun.misc.Contended
是什么,得从缓存说起
缓存与内存的速度比较
因为 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 将对象预读至缓存时占用不同的缓存行,这样,不会造成对方缓存行的失效
累加主要调用下面的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public void add (long x) { Cell[] as; long b, v; int m; Cell a; if ((as = cells) != null || !casBase(b = base, b + x)) { boolean uncontended = true ; if ( as == null || (m = as.length - 1 ) < 0 || (a = as[getProbe() & m]) == null || !(uncontended = a.cas(v = a.value, v + x)) ) { longAccumulate(x, null , uncontended); } } }
总结 :
如果已经有了累加数组
或给base累加发生了竞争导致失败
如果累加数组没有创建
或者累加数组长度为1
或者当前线程还没有对应的cell
或者累加cell失败
否者说明累加成功,退出。 否则累加成功 add 流程图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 final void longAccumulate (long x, LongBinaryOperator fn, boolean wasUncontended) { int h; if ((h = getProbe()) == 0 ) { ThreadLocalRandom.current(); h = getProbe(); wasUncontended = true ; } boolean collide = false ; for (;;) { Cell[] as; Cell a; int n; long v; if ((as = cells) != null && (n = as.length) > 0 ) { if ((a = as[(n - 1 ) & h]) == null ) { if (cellsBusy == 0 ) { Cell r = new Cell (x); if (cellsBusy == 0 && casCellsBusy()) { boolean created = false ; try { Cell[] rs; int m, j; if ((rs = cells) != null && (m = rs.length) > 0 && rs[j = (m - 1 ) & h] == null ) { rs[j] = r; created = true ; } } finally { cellsBusy = 0 ; } if (created) break ; continue ; } } collide = false ; } else if (!wasUncontended) wasUncontended = true ; else if (a.cas(v = a.value, ((fn == null ) ? v + x : fn.applyAsLong(v, x)))) break ; else if (n >= NCPU || cells != as) collide = false ; else if (!collide) collide = true ; else if (cellsBusy == 0 && casCellsBusy()) { continue ; } h = advanceProbe(h); } else if (cellsBusy == 0 && cells == as && casCellsBusy()) { boolean init = false ; try { if (cells == as) { Cell[] rs = new Cell [2 ]; rs[h & 1 ] = new Cell (x); cells = rs; init = true ; } } finally { cellsBusy = 0 ; } if (init) break ; } else if (casBase(v = base, ((fn == null ) ? v + x : fn.applyAsLong(v, x)))) break ; } }
总结:
先判断当前线程有没有对应的Cell
如果没有,随机生成一个值,这个值与当前线程绑定,通过这个值的取模运算定位当前线程Cell的位置。 进入for循环
longAccumulate 流程图
每个线程刚进入 longAccumulate 时,会尝试对应一个 cell 对象(找到一个坑位)
获取最终结果通过 sum 方法
1 2 3 4 5 6 7 8 9 10 11 public long sum () { Cell[] as = cells; Cell a; long sum = base; if (as != null ) { for (int i = 0 ; i < as.length; ++i) { if ((a = as[i]) != null ) sum += a.value; } } return sum; }
与运算和取模的关系 参考链接 :https://www.cnblogs.com/thrillerz/p/4530108.html
6.8 Unsafe 概述 Unsafe 对象提供了非常底层的,操作内存、线程的方法,Unsafe 对象不能直接调用,只能通过反射获得。jdk8直接调用Unsafe.getUnsafe()
获得的unsafe不能用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class UnsafeAccessor { static Unsafe unsafe; static { try { Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe" ); theUnsafe.setAccessible(true ); unsafe = (Unsafe) theUnsafe.get(null ); } catch (NoSuchFieldException | IllegalAccessException e) { throw new Error (e); } } static Unsafe getUnsafe () { return unsafe; } }
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public final native boolean compareAndSwapObject (Object var1, long var2, Object var4, Object var5) ;public final native boolean compareAndSwapInt (Object var1, long var2, int var4, int var5) ;public final native boolean compareAndSwapLong (Object var1, long var2, long var4, long var6) ;public final int getAndAddInt (Object var1, long var2, int var4) { int var5; do { var5 = this .getIntVolatile(var1, var2); } while (!this .compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }public final long getAndAddLong (Object var1, long var2, long var4) { long var6; do { var6 = this .getLongVolatile(var1, var2); } while (!this .compareAndSwapLong(var1, var2, var6, var6 + var4)); return var6; }public final int getAndSetInt (Object var1, long var2, int var4) { int var5; do { var5 = this .getIntVolatile(var1, var2); } while (!this .compareAndSwapInt(var1, var2, var5, var4)); return var5; }public final long getAndSetLong (Object var1, long var2, long var4) { long var6; do { var6 = this .getLongVolatile(var1, var2); } while (!this .compareAndSwapLong(var1, var2, var6, var4)); return var6; }public final Object getAndSetObject (Object var1, long var2, Object var4) { Object var5; do { var5 = this .getObjectVolatile(var1, var2); } while (!this .compareAndSwapObject(var1, var2, var5, var4));
Unsafe CAS 操作 unsafe实现字段更新 1 2 3 4 5 @Data class Student { volatile int id; volatile String name; }
1 2 3 4 5 6 7 8 9 10 11 Unsafe unsafe = UnsafeAccessor.getUnsafe();Field id = Student.class.getDeclaredField("id" );Field name = Student.class.getDeclaredField("name" );long idOffset = UnsafeAccessor.unsafe.objectFieldOffset(id);long nameOffset = UnsafeAccessor.unsafe.objectFieldOffset(name);Student student = new Student (); UnsafeAccessor.unsafe.compareAndSwapInt(student, idOffset, 0 , 20 ); UnsafeAccessor.unsafe.compareAndSwapObject(student, nameOffset, null , "张三" ); System.out.println(student);
输出
unsafe实现原子整数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class AtomicData { private volatile int data; static final Unsafe unsafe; static final long DATA_OFFSET; static { unsafe = UnsafeAccessor.getUnsafe(); try { DATA_OFFSET = unsafe.objectFieldOffset(AtomicData.class.getDeclaredField("data" )); } catch (NoSuchFieldException e) { throw new Error (e); } } public AtomicData (int data) { this .data = data; } public void decrease (int amount) { int oldValue; while (true ) { oldValue = data; if (unsafe.compareAndSwapInt(this , DATA_OFFSET, oldValue, oldValue - amount)) { return ; } } } public int getData () { return data; } }
Account 实现
1 2 3 4 5 6 7 8 9 10 11 Account.demo(new Account () { AtomicData atomicData = new AtomicData (10000 ); @Override public Integer getBalance () { return atomicData.getData(); } @Override public void withdraw (Integer amount) { atomicData.decrease(amount); } });
手动实现原子整数完整版+测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 public class UnsafeAtomicTest { public static void main (String[] args) { AccountImpl account = new AccountImpl (10000 ); account.demo(); } }interface Account { int getBalance () ; void decrease (int amount) ; default void demo () { ArrayList<Thread> ts = new ArrayList <>(1000 ); for (int i = 0 ; i < 1000 ; i++) { ts.add(new Thread (() -> { decrease(10 ); })); } for (Thread t:ts) { t.start(); } for (Thread t:ts) { try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(getBalance()); } }class AccountImpl implements Account { UnsafeAtomicInteger balance; public AccountImpl (int balance) { this .balance = new UnsafeAtomicInteger (balance); } @Override public int getBalance () { return balance.get(); } @Override public void decrease (int amount) { balance.getAndAccumulate(amount,(x,y) -> y - x); } }class UnsafeAtomicInteger { private volatile int value; private static final Unsafe unsafe; private static final long offset; static { unsafe = UnsafeAccessor.getUnsafe(); try { offset = unsafe.objectFieldOffset(UnsafeAtomicInteger.class.getDeclaredField("value" )); } catch (NoSuchFieldException e) { e.printStackTrace(); throw new Error (e); } } public UnsafeAtomicInteger () { } public UnsafeAtomicInteger (int value) { this .value = value; } public final int get () { return value; } public final boolean compareAndSet (int expext,int update) { return unsafe.compareAndSwapInt(this , offset, expext, update); } public final int getAndIncrement () { int oldValue; while (true ){ oldValue = value; if (unsafe.compareAndSwapInt(this ,offset,oldValue,oldValue + 1 )){ return oldValue; } } } public final int incrementAndGet () { int oldValue; while (true ){ oldValue = value; if (unsafe.compareAndSwapInt(this , offset, oldValue, oldValue + 1 )) { return oldValue + 1 ; } } } public final int getAndDecrement () { int oldValue; while (true ){ oldValue = value; if (unsafe.compareAndSwapInt(this , offset, oldValue, oldValue - 1 )) { return oldValue; } } } public final int decrementAndGet () { int oldValue; while (true ){ oldValue = value; if (unsafe.compareAndSwapInt(this , offset, oldValue, oldValue - 1 )) { return oldValue - 1 ; } } } public final int getAndUpdate (IntUnaryOperator operator) { int oldValue; int newValue; while (true ){ oldValue = value; newValue = operator.applyAsInt(oldValue); if (unsafe.compareAndSwapInt(this , offset, oldValue, newValue)) { return oldValue; } } } public final int updateAndGet (IntUnaryOperator operator) { int oldValue; int newValue; while (true ){ oldValue = value; newValue = operator.applyAsInt(oldValue); if (unsafe.compareAndSwapInt(this , offset, oldValue, newValue)) { return newValue; } } } public final int getAndAccumulate (int x, IntBinaryOperator operator) { int oldValue; int newValue; while (true ){ oldValue = value; newValue = operator.applyAsInt(x,oldValue); if (unsafe.compareAndSwapInt(this , offset, oldValue, newValue)) { return newValue; } } } public final int accumulateAndGet (int x, IntBinaryOperator operator) { int oldValue; int newValue; while (true ){ oldValue = value; newValue = operator.applyAsInt(x,oldValue); if (unsafe.compareAndSwapInt(this , offset, oldValue, newValue)) { return oldValue; } } } }class UnsafeAccessor { public static Unsafe getUnsafe () { Field field; Unsafe unsafe = null ; try { field = Unsafe.class.getDeclaredField("theUnsafe" ); field.setAccessible(true ); unsafe = (Unsafe)field.get(null ); } catch (Exception e) { e.printStackTrace(); } return unsafe; } }
6.9 自定义cas锁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class LockCas { 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 ); } }
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 LockCas lock = new LockCas ();new Thread (() -> { log.debug("begin..." ); lock.lock(); try { log.debug("lock..." ); sleep(1 ); } finally { lock.unlock(); } }).start();new Thread (() -> { log.debug("begin..." ); lock.lock(); try { log.debug("lock..." ); } finally { lock.unlock(); } }).start();
1 2 3 4 5 6 18:27:07.198 c.Test42 [Thread-0] - begin... 18:27:07.202 c.Test42 [Thread-0] - lock... 18:27:07.198 c.Test42 [Thread-1] - begin... 18:27:08.204 c.Test42 [Thread-0] - unlock... 18:27:08.204 c.Test42 [Thread-1] - lock... 18:27:08.204 c.Test42 [Thread-1] - unlock...
本章小结 CAS 与 volatile API Unsafe *原理方面 7.共享模型之不可变 7.1 日期转换的问题 问题提出 下面的代码在运行时,由于 SimpleDateFormat 不是线程安全的
1 2 3 4 5 6 7 8 9 10 SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd" );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 或者出现不正确的日期解析结果,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 19:10:40.859 [Thread-2] c.TestDateParse - {} java.lang.NumberFormatException: For input string: "" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Long.parseLong(Long.java:601) at java.lang.Long.parseLong(Long.java:631) at java.text.DigitList.getLong(DigitList.java:195) at java.text.DecimalFormat.parse(DecimalFormat.java:2084) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514) at java.text.DateFormat.parse(DateFormat.java:364) at cn.itcast.n7.TestDateParse.lambda$test1$0 (TestDateParse.java:18) at java.lang.Thread.run(Thread.java:748) 19:10:40.859 [Thread-1] c.TestDateParse - {} java.lang.NumberFormatException: empty String at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842) at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110) at java.lang.Double.parseDouble(Double.java:538) at java.text.DigitList.getDouble(DigitList.java:169) at java.text.DecimalFormat.parse(DecimalFormat.java:2089) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514) at java.text.DateFormat.parse(DateFormat.java:364) at cn.itcast.n7.TestDateParse.lambda$test1$0 (TestDateParse.java:18) at java.lang.Thread.run(Thread.java:748) 19:10:40.857 [Thread-8] c.TestDateParse - Sat Apr 21 00:00:00 CST 1951 19:10:40.857 [Thread-9] c.TestDateParse - Sat Apr 21 00:00:00 CST 1951 19:10:40.857 [Thread-6] c.TestDateParse - Sat Apr 21 00:00:00 CST 1951 19:10:40.857 [Thread-4] c.TestDateParse - Sat Apr 21 00:00:00 CST 1951 19:10:40.857 [Thread-5] c.TestDateParse - Mon Apr 21 00:00:00 CST 178960645 19:10:40.857 [Thread-0] c.TestDateParse - Sat Apr 21 00:00:00 CST 1951 19:10:40.857 [Thread-7] c.TestDateParse - Sat Apr 21 00:00:00 CST 1951 19:10:40.857 [Thread-3] c.TestDateParse - Sat Apr 21 00:00:00 CST 1951
思路 - 同步锁 这样虽能解决问题,但带来的是性能上的损失,并不算很好:
1 2 3 4 5 6 7 8 9 10 11 12 SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd" );for (int i = 0 ; i < 50 ; i++) { new Thread (() -> { synchronized (sdf) { try { log.debug("{}" , sdf.parse("1951-04-21" )); } catch (Exception e) { log.error("{}" , e); } } }).start(); }
思路 - 不可变 如果一个对象在不能够修改其内部状态(属性),那么它就是线程安全的,因为不存在并发修改啊!这样的对象在 Java 中有很多,例如在 Java 8 后,提供了一个新的日期格式化类:
1 2 3 4 5 6 7 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(); }
可以看 DateTimeFormatter 的文档:
不可变对象,实际是另一种避免竞争的方式。
7.2 不可变设计 String类的设计 另一个大家更为熟悉的 String 类也是不可变的,以它为例,说明一下不可变设计的要素
1 2 3 4 5 6 7 8 9 10 public final class String implements java .io.Serializable, Comparable<String>, CharSequence { private final char value[]; private int hash; }
说明:
将类声明为final,避免被带外星方法的子类继承,从而破坏了不可变性。 将字符数组声明为final,避免被修改 hash虽然不是final的,但是其只有在调用hash()
方法的时候才被赋值,除此之外再无别的方法修改。 final 的使用 发现该类、类中所有属性都是 final 的
属性用 final 修饰保证了该属性是只读的,不能修改 类用 final 修饰保证了该类中的方法不能被覆盖,防止子类无意间破坏不可变性 保护性拷贝 但有同学会说,使用字符串时,也有一些跟修改相关的方法啊,比如 substring 等,那么下面就看一看这些方法是 如何实现的,就以 substring 为例:
1 2 3 4 5 6 7 8 9 10 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 做出 了修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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)】
*模式之享元 简介
定义 英文名称:Flyweight pattern. 当需要重用数量有限的同一类对象时
wikipedia: A flyweight is an object that minimizes memory usage by sharing as much data as possible with other similar objects
出自 "Gang of Four" design patterns
归类 Structual patterns
体现
包装类
在JDK中 Boolean,Byte,Short,Integer,Long,Character 等包装类提供了 valueOf 方法,例如 Long 的 valueOf 会缓存 -128~127 之间的 Long 对象,在这个范围之间会重用对象,大于这个范围,才会新建 Long 对象:
1 2 3 4 5 6 7 public static Long valueOf (long l) { final int offset = 128 ; if (l >= -128 && l <= 127 ) { return 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 String 串池 (不可变、线程安全)
详见jvm
BigDecimal BigInteger (不可变、线程安全)
一部分数字使用了享元模式进行了缓存。
手动实现一个连接池 例如:一个线上商城应用,QPS 达到数千,如果每次都重新创建和关闭数据库连接,性能会受到极大影响。 这时 预先创建好一批连接,放入连接池。一次请求到达后,从连接池获取连接,使用完毕后再还回连接池,这样既节约 了连接的创建和关闭时间,也实现了连接的重用,不至于让庞大的连接数压垮数据库。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 class Pool { private final int poolSize; private Connection[] connections; private AtomicIntegerArray states; 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 )); } } 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(); } } } } 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 { }
使用连接池:
1 2 3 4 5 6 7 8 9 10 11 12 Pool pool = new Pool (2 );for (int i = 0 ; i < 5 ; i++) { new Thread (() -> { Connection conn = pool.borrow(); try { Thread.sleep(new Random ().nextInt(1000 )); } catch (InterruptedException e) { e.printStackTrace(); } pool.free(conn); }).start(); }
以上实现没有考虑:
连接的动态增长与收缩 连接保活(可用性检测) 等待超时处理 分布式 hash 对于关系型数据库,有比较成熟的连接池实现,例如c3p0, druid等 对于更通用的对象池,可以考虑使用apache commons pool,例如redis连接池可以参考jedis中关于连接池的实现
* 原理之 final 设置 final 变量的原理 理解了 volatile 原理,再对比 final 的实现就比较简单了
1 2 3 public class TestFinal { final int a = 20 ; }
字节码
1 2 3 4 5 6 7 0: aload_0 1: invokespecial 4: aload_0 5: bipush 20 7: putfield <-- 写屏障 10: return
发现 final 变量的赋值也会通过 putfield 指令来完成,同样在这条指令之后也会加入写屏障,这样对final变量的写入不会重排序到构造方法之外,保证在其它线程读到 它的值时不会出现为 0 的情况。普通变量不能保证这一点了。
读取final变量原理 有以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public class TestFinal { final static int A = 10 ; final static int B = Short.MAX_VALUE+1 ; final int a = 20 ; final int b = Integer.MAX_VALUE; final void test1 () { final int c = 30 ; new Thread (()->{ System.out.println(c); }).start(); final int d = 30 ; class Task implements Runnable { @Override public void run () { System.out.println(d); } } new Thread (new Task ()).start(); } }class UseFinal1 { public void test () { System.out.println(TestFinal.A); System.out.println(TestFinal.B); System.out.println(new TestFinal ().a); System.out.println(new TestFinal ().b); new TestFinal ().test1(); } }class UseFinal2 { public void test () { System.out.println(TestFinal.A); } }
反编译UseFinal1中的test方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 public test () V L0 LINENUMBER 31 L0 GETSTATIC java/lang/System.out : Ljava/io/PrintStream; BIPUSH 10 INVOKEVIRTUAL java/io/PrintStream.println (I)V L1 LINENUMBER 32 L1 GETSTATIC java/lang/System.out : Ljava/io/PrintStream; LDC 32768 INVOKEVIRTUAL java/io/PrintStream.println (I)V L2 LINENUMBER 33 L2 GETSTATIC java/lang/System.out : Ljava/io/PrintStream; NEW cn/itcast/n5/TestFinal DUP INVOKESPECIAL cn/itcast/n5/TestFinal.<init> ()V INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class; POP BIPUSH 20 INVOKEVIRTUAL java/io/PrintStream.println (I)V L3 LINENUMBER 34 L3 GETSTATIC java/lang/System.out : Ljava/io/PrintStream; NEW cn/itcast/n5/TestFinal DUP INVOKESPECIAL cn/itcast/n5/TestFinal.<init> ()V INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class; POP LDC 2147483647 INVOKEVIRTUAL java/io/PrintStream.println (I)V L4 LINENUMBER 35 L4 NEW cn/itcast/n5/TestFinal DUP INVOKESPECIAL cn/itcast/n5/TestFinal.<init> ()V INVOKEVIRTUAL cn/itcast/n5/TestFinal.test1 ()V L5 LINENUMBER 36 L5 RETURN L6 LOCALVARIABLE this Lcn/itcast/n5/UseFinal1; L0 L6 0 MAXSTACK = 3 MAXLOCALS = 1 }
可以看见,jvm对final变量的访问做出了优化:另一个类中的方法调用final变量是,不是从final变量所在类中获取(共享内存),而是直接复制一份到方法栈栈帧中的操作数栈中(工作内存),这样可以提升效率,是一种优化。
总结:
对于较小的static final变量:复制一份到操作数栈中 对于较大的static final变量:复制一份到当前类的常量池中 对于非静态final变量,优化同上。 final总结 final关键字的好处:
(1)final关键字提高了性能。JVM和Java应用都会缓存final变量。
(2)final变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销。
(3)使用final关键字,JVM会对方法、变量及类进行优化。
关于final的重要知识点
1、final关键字可以用于成员变量、本地变量、方法以及类。
2、final成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误。
3、你不能够对final变量再次赋值。
4、本地变量必须在声明时赋值。
5、在匿名类中所有变量都必须是final变量。
6、final方法不能被重写。
7、final类不能被继承。
8、final关键字不同于finally关键字,后者用于异常处理。
9、final关键字容易与finalize()方法搞混,后者是在Object类中定义的方法,是在垃圾回收之前被JVM调用的方法。
10、接口中声明的所有变量本身是final的。
11、final和abstract这两个关键字是反相关的,final类就不可能是abstract的。
12、final方法在编译阶段绑定,称为静态绑定(static binding)。
13、没有在声明时初始化final变量的称为空白final变量(blank final variable),它们必须在构造器中初始化,或者调用this()初始化。不这么做的话,编译器会报错“final变量(变量名)需要进行初始化”。
14、将类、方法、变量声明为final能够提高性能,这样JVM就有机会进行估计,然后优化。
15、按照Java代码惯例,final变量就是常量,而且通常常量名要大写。
16、对于集合对象声明为final指的是引用不能被更改,但是你可以向其中增加,删除或者改变内容。
参考链接:Java中final实现原理的深入分析(附示例)-java教程-PHP中文网
7.3 无状态 在 web 阶段学习时,设计 Servlet 时为了保证其线程安全,都会有这样的建议,不要为 Servlet 设置成员变量,这 种没有任何成员变量的类是线程安全的 。
因为成员变量保存的数据也可以称为状态信息,因此没有成员变量就称之为【无状态】
问题 什么时候将导致用户态到内核态的转变?在synchroniezed进行加锁的时候。 final是怎么优化读取速度的?复习完jvm再看就懂了。[视频](