用英文字母做网站关键词,seo技术培训山东,房地产市场调查报告,网站更改模板 seo一、问题提出
1.1 需求描述 有如下的需求#xff0c;需要保证 account.withdraw() 取款方法的线程安全#xff0c;代码如下#xff1a;
interface Account {// 获取余额Integer getBalance();// 取款void withdraw(Integer amount);/*** 方法内会启动 1000 个线程#xf…一、问题提出
1.1 需求描述 有如下的需求需要保证 account.withdraw() 取款方法的线程安全代码如下
interface Account {// 获取余额Integer getBalance();// 取款void withdraw(Integer amount);/*** 方法内会启动 1000 个线程每个线程做 -10 元 的操作* 如果初始余额为 10000 那么正确的结果应当是 0*/static void demo(Account account) {ListThread 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);}
}
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.2 问题分析 为什么会出现线程安全问题是因为在多线程的环境下取款的 withdraw() 方法里面是临界区存在指令交错的行为。
1.3 加锁解决 首先想到的解决方式就是给 Account 对象加锁如下代码
class AccountUnsafe implements Account {private Integer balance;public AccountUnsafe(Integer balance) {this.balance balance;}Overridepublic Integer getBalance() {synchronized (this){return balance;}}Overridepublic void withdraw(Integer amount) {synchronized (this){balance - amount;}}public static void main(String[] args) {Account.demo(new AccountUnsafe(10000));}
} 运行结果如下没有任何问题。 1.4 无锁解决 也可以通过无锁的方式解决上述的问题如下代码
public class AccountSafe implements Account{private AtomicInteger balance;public AccountSafe(Integer balance) {this.balance new AtomicInteger(balance);}Overridepublic Integer getBalance() {return balance.get();}Overridepublic void withdraw(Integer amount) {while(true){int prev balance.get();int next prev - amount;if(balance.compareAndSet(prev,next)){break;}}}public static void main(String[] args) {Account.demo(new AccountSafe(10000));}
} 运行结果如下没有任何问题。 二、CAS 与 volatile
2.1 CAS 在上一小结看到的使用 AtomicInteger 的解决方法内部并没有用锁来保护共享变量的线程安全。那么它是如何实现的呢 Overridepublic void withdraw(Integer amount) {// 需要不断尝试直到成功为止while(true){// 比如拿到了旧值 1000int prev balance.get();// 在这个基础上 1000-10 990int next prev - amount;/** compareAndSet 会做一个检查在 set 值之前先比较 prev 和当前值* 若 prev 值和当前值一致则用 next 设置为新值并返回 true 表示成功。* 若 prev 值和当前值不一致则 next 作废返回 false 表示失败进入 while 下次循环重试* */if(balance.compareAndSet(prev,next)){break;}}} 这里面最关键的就是 compareAndSet() 方法它的简称就是 CAS也有 compare and swap 的说法此方法是一个原子操作。 其实 CAS 的底层是 lock cmpxchg 指令在单核 CPU 和多核 CPU 下都能够保证原子性。
2.2 volatile 获取共享变量时为了保证该变量的可见性需要使用 volatile 修饰。 它可以用来修饰成员变量和静态成员变量他可以避免线程从自己的工作缓存中查找变量的值必须到主存中获取它的值线程操作 volatile 变量都是直接操作主存。即一个线程对 volatile 变量的修改对另一个线程可见。 volatile 仅仅保证了共享变量的可见性让其它线程能够看到最新值但不能解决指令交错问题不能保证原子性 CAS 必须借助 volatile 才能读取到共享变量的最新值来实现【比较并交换】的效果。
2.3 为什么无锁效率高 无锁情况下即使重试失败线程始终在高速运行没有停歇而 synchronized 会让线程在没有获得锁的时候发生上下文切换进入阻塞。 但无锁情况下因为线程要保持运行需要额外 CPU 的支持CPU 在这里就好比高速跑道没有额外的跑道线程想高速运行也无从谈起虽然不会进入阻塞但由于没有分到时间片仍然会进入可运行状态还是会导致上下文切换。
2.4 CAS 的特点 将 CAS 和 volatile 结合使用可以实现无锁并发适用于线程数少、多核 CPU 的场景下。 CAS 是基于乐观锁的思想最乐观的估计不怕别的线程来修改共享变量就算改了也没关系我吃亏点再重试。 synchronized 是基于悲观锁的思想最悲观的估计得防着其它线程来修改共享变量我上了锁你们都别想改我改完了解开锁你们才有机会 CAS 体现的是无锁并发、无阻塞并发。因为没有使用 synchronized所以线程不会陷入阻塞这是效率提升的因素之一但如果竞争激烈可以想到重试必然频繁发生反而效率会受影响 。
三、原子整数 在 JUC 并发包下提供了一些原子整数的工具类如AtomicBoolean、AtomicInteger 和 AtomicLong这几个类的用法类似下面就以 AtomicInteger 为例介绍下常用的方法。
public class AtomicTest {public static void main(String[] args) {// 无参构造的初始值为 0有参构造的初始值需要自己指定AtomicInteger i new AtomicInteger(0);// 获取并自增返回 0类似于 iSystem.out.println(i.getAndIncrement());// 自增并获取i 1, 结果 i 2, 返回 2类似于 iSystem.out.println(i.incrementAndGet());// 自减并获取i 2, 结果 i 1, 返回 1类似于 --iSystem.out.println(i.decrementAndGet());// 获取并自减i 1, 结果 i 0, 返回 1类似于 i--System.out.println(i.getAndDecrement());// 获取并加值i 0, 结果 i 5, 返回 0System.out.println(i.getAndAdd(5));// 加值并获取i 5, 结果 i 0, 返回 0System.out.println(i.addAndGet(-5));// 获取并更新i 0, p 为 i 的当前值, 结果 i -2, 返回 0// 其中函数中的操作能保证原子但函数需要无副作用System.out.println(i.getAndUpdate(p - p - 2));// 更新并获取i -2, p 为 i 的当前值, 结果 i 0, 返回 0// 其中函数中的操作能保证原子但函数需要无副作用System.out.println(i.updateAndGet(p - p 2));// 获取并计算i 0, p 为 i 的当前值, x 为参数1, 结果 i 10, 返回 0// 其中函数中的操作能保证原子但函数需要无副作用// getAndUpdate 如果在 lambda 中引用了外部的局部变量要保证该局部变量是 final 的// getAndAccumulate 可以通过 参数1 来引用外部的局部变量但因为其不在 lambda 中因此不必是 finalSystem.out.println(i.getAndAccumulate(10, (p, x) - p x));// 计算并获取i 10, p 为 i 的当前值, x 为参数1, 结果 i 0, 返回 0// 其中函数中的操作能保证原子但函数需要无副作用System.out.println(i.accumulateAndGet(-10, (p, x) - p x));}
}
四、原子引用 为什么需要原子引用因为共享的数据并不一定都是基本数据类型的还有可能是小数类型那么我们就可以使用原子引用来保证其中的共享变量操作时的线程安全。 原子引用分为如下几种AtomicReference、AtomicMarkableReference、AtomicStampedReference
4.1 AtomicReference 以最开始的例子为例假设此时的账户余额是 BigDecimal 类型我们就需要使用 AtomicReferenceAtomicReference如下代码
interface Account {// 获取余额BigDecimal getBalance();// 取款void withdraw(BigDecimal amount);/*** 方法内会启动 1000 个线程每个线程做 -10 元 的操作* 如果初始余额为 10000 那么正确的结果应当是 0*/static void demo(Account account) {ListThread ts new ArrayList();long start System.nanoTime();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();}});long end System.nanoTime();System.out.println(account.getBalance() cost: (end - start) / 1000_000 ms);}
}
public class AccountSafe implements Account{private AtomicReferenceBigDecimal balance;public AccountSafe(BigDecimal balance) {this.balance new AtomicReference(balance);}Overridepublic BigDecimal getBalance() {return balance.get();}Overridepublic void withdraw(BigDecimal amount) {// 需要不断尝试直到成功为止while(true){BigDecimal prev balance.get();// 调用 subtract 相当于减的操作BigDecimal next prev.subtract(amount);if(balance.compareAndSet(prev,next)){break;}}}public static void main(String[] args) {Account.demo(new AccountSafe(new BigDecimal(10000)));}
} 测试结果如下没有任何问题。 4.2 ABA 问题 主线程仅能判断出共享变量的值与最初值 A 是否相同不能感知到这种从 A 改为 B 又 改回 A 的情况这就是我们所说的 ABA 问题如下代码
Slf4j(topic c.test)
public class Main8 {static AtomicReferenceString 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, C));}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();}
} 输出结果如下 4.3 AtomicStampedReference 如果主线程希望只要有其它线程动过了共享变量那么自己的 cas 就算失败这时仅比较值是不够的需要再加一个版本号 AtomicStampedReference。 AtomicStampedReference 可以给原子引用加上版本号追踪原子引用整个的变化过程如 A - B - A -C 通过 AtomicStampedReference我们可以知道引用变量中途被更改了几次。如下代码
Slf4j(topic c.test)
public class Main9 {// 不光给变量一个初始值还给一个初始的版本号static AtomicStampedReferenceString 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);// 尝试改为 C此时 compareAndSet 方法需要四个参数当前值期望值当前版本号期望版本号log.debug(change A-C {}, ref.compareAndSet(prev, C, stamp, stamp 1));}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();}
} 可以看到解决了 ABA 问题更新并没有成功。 4.4 AtomicMarkableReference 但是有时候并不关心引用变量更改了几次只是单纯的关心是否更改过用一个布尔值就可以搞定所以就有了 AtomicMarkableReference。 如下案例主人要检查垃圾袋满没满是否需要倒垃圾如果满了则更换新的垃圾袋如果还空着呢就用原有的垃圾袋。此时还有另外一个线程保洁阿姨她负责倒空垃圾袋里面的垃圾但是她还是用原来的垃圾袋如果此时主人检查垃圾袋是空的就不用再去更换垃圾袋了。 代码如下所示
Slf4j(topic c.test)
public class Main9 {public static void main(String[] args) throws InterruptedException {GarbageBag bag new GarbageBag(装满了垃圾);// 参数2 mark 可以看作一个标记表示垃圾袋满了AtomicMarkableReferenceGarbageBag 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());}
}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;}
} 输出结果如下所示 五、原子数组 原子数据保护的是数组里面的元素常用的原子数组类是 AtomicIntegerArray、AtomicLongArray 和 AtomicReferenceArray测试类的代码如下所示 Slf4j(topic c.test)
public class Main9 {public static void main(String[] args) throws InterruptedException {// 不安全的数组demo(() - new int[10],(array) - array.length,(array, index) - array[index],array - System.out.println(Arrays.toString(array)));// 安全的数组demo(() - new AtomicIntegerArray(10),(array) - array.length(),(array, index) - array.getAndIncrement(index),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(SupplierT arraySupplier,FunctionT, Integer lengthFun,BiConsumerT, Integer putConsumer,ConsumerT printConsumer) {ListThread ts new ArrayList();T array arraySupplier.get();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);}
} 输出结果如下所示 六、字段更新器 字段更新器保护的是对象里面的某个属性即对这个属性进行原子操作但是需要配合 volatile 修饰的字段使用否则会出现异常如下代码
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);// 修改成功 field 10System.out.println(test5.field);// 修改成功 field 20fieldUpdater.compareAndSet(test5, 10, 20);System.out.println(test5.field);// 修改失败 field 20fieldUpdater.compareAndSet(test5, 10, 30);System.out.println(test5.field);}
} 输出结果如下 七、原子累加器 累加器顾名思义就是对一个整数进行累加的操作在 jdk8 以后新增了几个专门用于累加操作的工具类比如LongAdder、LongAccumulator 等他们的性能要比我们的 AtomicLong 性能要高很多。 性能提升的原因很简单就是在有竞争时设置多个累加单元Therad-0 累加 Cell[0]而 Thread-1 累加 Cell[1]... 最后将结果汇总。这样它们在累加时操作的不同的 Cell 变量因此减少了 CAS 重试失败从而提高性能。如下测试代码
public class Test5 {public static void main(String[] args) {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());}}private static T void demo(SupplierT adderSupplier, ConsumerT action) {T adder adderSupplier.get();long start System.nanoTime();ListThread 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);}
} 输出结果如下所示可以看到相差的时间还是蛮大的
文章转载自: http://www.morning.cwskn.cn.gov.cn.cwskn.cn http://www.morning.lwnwl.cn.gov.cn.lwnwl.cn http://www.morning.pmptm.cn.gov.cn.pmptm.cn http://www.morning.bhqlj.cn.gov.cn.bhqlj.cn http://www.morning.rxhsm.cn.gov.cn.rxhsm.cn http://www.morning.qjfkz.cn.gov.cn.qjfkz.cn http://www.morning.lqws.cn.gov.cn.lqws.cn http://www.morning.gdpai.com.cn.gov.cn.gdpai.com.cn http://www.morning.rswfj.cn.gov.cn.rswfj.cn http://www.morning.hlxpz.cn.gov.cn.hlxpz.cn http://www.morning.ryxbz.cn.gov.cn.ryxbz.cn http://www.morning.yzmzp.cn.gov.cn.yzmzp.cn http://www.morning.pbmg.cn.gov.cn.pbmg.cn http://www.morning.rcgzg.cn.gov.cn.rcgzg.cn http://www.morning.wlfxn.cn.gov.cn.wlfxn.cn http://www.morning.tgczj.cn.gov.cn.tgczj.cn http://www.morning.zgdnd.cn.gov.cn.zgdnd.cn http://www.morning.rrxnz.cn.gov.cn.rrxnz.cn http://www.morning.ydxwj.cn.gov.cn.ydxwj.cn http://www.morning.dschz.cn.gov.cn.dschz.cn http://www.morning.jpdbj.cn.gov.cn.jpdbj.cn http://www.morning.lsnnc.cn.gov.cn.lsnnc.cn http://www.morning.hkswt.cn.gov.cn.hkswt.cn http://www.morning.mttck.cn.gov.cn.mttck.cn http://www.morning.cwjxg.cn.gov.cn.cwjxg.cn http://www.morning.lbxcc.cn.gov.cn.lbxcc.cn http://www.morning.pmsl.cn.gov.cn.pmsl.cn http://www.morning.krswn.cn.gov.cn.krswn.cn http://www.morning.dbrpl.cn.gov.cn.dbrpl.cn http://www.morning.jydky.cn.gov.cn.jydky.cn http://www.morning.playmi.cn.gov.cn.playmi.cn http://www.morning.wmqxt.cn.gov.cn.wmqxt.cn http://www.morning.rmmz.cn.gov.cn.rmmz.cn http://www.morning.bnlkc.cn.gov.cn.bnlkc.cn http://www.morning.wchsx.cn.gov.cn.wchsx.cn http://www.morning.kgxrq.cn.gov.cn.kgxrq.cn http://www.morning.rywn.cn.gov.cn.rywn.cn http://www.morning.hctgn.cn.gov.cn.hctgn.cn http://www.morning.yongkangyiyuan-pfk.com.gov.cn.yongkangyiyuan-pfk.com http://www.morning.snnkt.cn.gov.cn.snnkt.cn http://www.morning.dpfr.cn.gov.cn.dpfr.cn http://www.morning.yprnp.cn.gov.cn.yprnp.cn http://www.morning.mggwr.cn.gov.cn.mggwr.cn http://www.morning.zsyrk.cn.gov.cn.zsyrk.cn http://www.morning.xcbnc.cn.gov.cn.xcbnc.cn http://www.morning.mplld.cn.gov.cn.mplld.cn http://www.morning.rwjfs.cn.gov.cn.rwjfs.cn http://www.morning.mhfbp.cn.gov.cn.mhfbp.cn http://www.morning.tbqxh.cn.gov.cn.tbqxh.cn http://www.morning.rbgwj.cn.gov.cn.rbgwj.cn http://www.morning.lffrh.cn.gov.cn.lffrh.cn http://www.morning.gmysq.cn.gov.cn.gmysq.cn http://www.morning.pypbz.cn.gov.cn.pypbz.cn http://www.morning.ghrlx.cn.gov.cn.ghrlx.cn http://www.morning.jwncx.cn.gov.cn.jwncx.cn http://www.morning.ypdhl.cn.gov.cn.ypdhl.cn http://www.morning.xtqr.cn.gov.cn.xtqr.cn http://www.morning.lwbhw.cn.gov.cn.lwbhw.cn http://www.morning.qggxt.cn.gov.cn.qggxt.cn http://www.morning.pyxwn.cn.gov.cn.pyxwn.cn http://www.morning.mrbzq.cn.gov.cn.mrbzq.cn http://www.morning.rxnr.cn.gov.cn.rxnr.cn http://www.morning.bpmtj.cn.gov.cn.bpmtj.cn http://www.morning.pdmc.cn.gov.cn.pdmc.cn http://www.morning.ghgck.cn.gov.cn.ghgck.cn http://www.morning.ykrg.cn.gov.cn.ykrg.cn http://www.morning.sbqrm.cn.gov.cn.sbqrm.cn http://www.morning.bnbtp.cn.gov.cn.bnbtp.cn http://www.morning.jjzxn.cn.gov.cn.jjzxn.cn http://www.morning.lphtm.cn.gov.cn.lphtm.cn http://www.morning.xqbbc.cn.gov.cn.xqbbc.cn http://www.morning.fbbpj.cn.gov.cn.fbbpj.cn http://www.morning.mqdr.cn.gov.cn.mqdr.cn http://www.morning.qgfhr.cn.gov.cn.qgfhr.cn http://www.morning.dfhkh.cn.gov.cn.dfhkh.cn http://www.morning.lfbzg.cn.gov.cn.lfbzg.cn http://www.morning.jokesm.com.gov.cn.jokesm.com http://www.morning.mm27.cn.gov.cn.mm27.cn http://www.morning.ngzkt.cn.gov.cn.ngzkt.cn http://www.morning.mcpby.cn.gov.cn.mcpby.cn