asp.net网站开发案例教程,北京最贵商场,海南省建设与执业资格注册中心网站,共享虚拟主机 几个网站锁#xff08;lock#xff09;的代价 锁是用来做并发最简单的方式#xff0c;其代价也是最高的#xff0c;Java在JDK1.5之前都是靠synchronized关键字来加锁。但是加锁机制会有如下几个问题#xff1a; 加锁、释放锁会需要操作系统进行上下文切换和调度延时#xff0c;在… 锁lock的代价 锁是用来做并发最简单的方式其代价也是最高的Java在JDK1.5之前都是靠synchronized关键字来加锁。但是加锁机制会有如下几个问题 加锁、释放锁会需要操作系统进行上下文切换和调度延时在上下文切换的时候cpu之前缓存的指令和数据都将失效这个过程将增加系统开销。 多个线程同时竞争锁锁竞争机制本身需要消耗系统资源。没有获取到锁的线程会被挂起直至获取锁在线程被挂起和恢复执行的过程中也存在很大开销。 等待锁的线程会阻塞影响实际的使用体验。如果被阻塞的线程优先级高而持有锁的线程优先级低将会导致优先级反转(Priority Inversion)。 乐观锁与悲观锁 悲观锁是认为别的线程会修改值。 独占锁是一种悲观锁synchronized就是一种独占锁。synchronized加锁后就能够确保程序执行时不会被其它线程干扰得到正确的结果。 乐观锁本质上是乐观的认为别的线程不会去修改值。如果发现值被修改了可以再次重试。CAS机制Compare And Swap就是一种乐观锁。 Compare And Swap CAS是一种有名的无锁lock-free算法。也是一种现代 CPU 广泛支持的CPU指令级的操作只有一步原子操作所以非常快。而且CAS避免了请求操作系统来裁定锁的问题不用麻烦操作系统直接在CPU内部就搞定了。 CAS有三个操作参数 内存位置V它的值是我们想要去更新的 预期原值A上一次从内存中读取的值 新值B应该写入的新值 CAS的操作过程将内存位置V的值与A比较compare如果相等则说明没有其它线程来修改过这个值所以把内存V的的值更新成Bswap如果不相等说明V上的值被修改过了不更新而是返回当前V的值再重新执行一次任务再继续这个过程。 所以当多个线程尝试使用CAS同时更新同一个变量时其中一个线程会成功更新变量的值剩下的会失败。失败的线程可以重试或者什么也不做。 简单来说CAS 的含义是“我认为原有的值应该是什么如果是则将原有的值更新为新值否则不做修改并告诉我这个值现在是多少”。这段描述引自《Java并发编程实践》 JVM对CAS的支持 在JDK1.5之前如果不编写明确的代码就无法执行CAS操作在JDK1.5中引入了底层的支持在int、long和对象的引用等类型上都公开了CAS的操作并且JVM把它们编译为底层硬件提供的最有效的方法在运行CAS的平台上运行时把它们编译为相应的机器指令如果处理器/CPU不支持CAS指令那么JVM将使用自旋锁。 在原子类变量中如java.util.concurrent.atomic包下的AtomicXXX都使用了这些底层的JVM支持为数字类型的引用类型提供一种高效的CAS操作而在java.util.concurrent中的大多数类在实现时都直接或间接的使用了这些原子变量类。 java.util.concurrent.atomic.AtomicLong源码中的自增getAndIncrement()方法 //1操作 public final long getAndIncrement() { while (true) { long current get(); long next current 1; //当1操作成功的时候直接返回退出此循环 if (compareAndSet(current, next)) return current; } } //调用JNI实现CAS public final boolean compareAndSet(long expect, long update) { return unsafe.compareAndSwapLong(this, valueOffset, expect, update); } JNI:Java Native Interface为JAVA本地调用允许java调用其他语言。在jdk1.8后getAndIncrement()方法已经看不到具体代码了而是封装在unsafe类里面。 CAS优缺点 缺点 CAS虽然很高效的解决原子操作但是CAS仍然存在三大问题。ABA问题循环时间长开销大和只能保证一个共享变量的原子操作。 ABA问题。 因为CAS需要在操作值的时候检查下值有没有发生变化如果没有发生变化则更新但是如果一个值原来是A变成了B又变成了A那么使用CAS进行检查时会发现它的值没有发生变化但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号每次变量更新的时候把版本号加一那么ABA 就会变成1A-2B3A。 从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用并且当前标志是否等于预期标志如果全部相等则以原子方式将该引用和该标志的值设置为给定的更新值。 循环时间长开销大。 自旋CAS如果长时间不成功会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升pause指令有两个作用第一它可以延迟流水线执行指令de-pipeline,使CPU不会消耗过多的执行资源延迟的时间取决于具体实现的版本在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突memory order violation而引起CPU流水线被清空CPU pipeline flush从而提高CPU的执行效率。 只能保证一个共享变量的原子操作。当对一个共享变量执行操作时我们可以使用循环CAS的方式来保证原子操作但是对多个共享变量操作时循环CAS就无法保证操作的原子性这个时候就可以用锁或者有一个取巧的办法就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i2,ja合并一下ij2a然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性你可以把多个变量放在一个对象里来进行CAS操作。 比较花费CPU资源即使没有任何争用也会做一些无用功。 会增加程序测试的复杂度稍不注意就会出现问题。 优点 可以保证变量操作的原子性 并发量不是很高的情况下使用CAS机制比使用锁机制效率更高 在线程对共享资源占用时间较短的情况下使用CAS机制效率也会较高。 Java提供的CAS操作类--Unsafe类 从Java5开始引入了对CAS机制的底层的支持在这之前需要开发人员编写相关的代码才可以实现CAS。在原子变量类Atomic**_中例如AtomicInteger、AtomicLong可以看到CAS操作的代码在这里的代码都是调用了底层核心代码调用native修饰的方法的实现方法。在AtomicInteger源码中可以看getAndSet方法和compareAndSet方法之间的关系compareAndSet方法调用了底层的实现该方法可以实现与一个volatile变量的读取和写入相同的效果。在前面说到了volatile不支持例如i这样的复合操作在Atomic*中提供了实现该操作的方法。JVM对CAS的支持通过这些原子类Atomic_暴露出来供我们使用。 而Atomic系类的类底层调用的是Unsafe类的APIUnsafe类提供了一系列的compareAndSwap*方法下面就简单介绍下Unsafe类的API long objectFieldOffsetField field方法返回指定的变量在所属类中的内存偏移地址该偏移地址仅仅在该Unsafe函数中访问指定字段时使用。如下代码使用Unsafe类获取变量value在AtomicLong对象中的内存偏移。 static {try { valueOffset unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField(value));} catch (Exception ex) { throw new Error(ex); }} int arrayBaseOffsetClass arrayClass方法获取数组中第一个元素的地址。 int arrayIndexScaleClass arrayClass方法获取数组中一个元素占用的字节。 boolean compareAndSwapLongObject obj, long offset, long expect, long update方法比较对象obj中偏移量为offset的变量的值是否与expect相等相等则使用update值更新然后返回true否则返回false。 public native long getLongvolatileObject obj, long offset方法获取对象obj中偏移量为offset的变量对应volatile语义的值。 void putLongvolatileObject obj, long offset, long value方法设置obj对象中offset偏移的类型为long的field的值为value支持volatile语义。 void putOrderedLongObject obj, long offset, long value方法设置obj对象中offset偏移地址对应的long型field的值为value。这是一个有延迟的putLongvolatile方法并且不保证值修改对其他线程立刻可见。只有在变量使用volatile修饰并且预计会被意外修改时才使用该方法。 void parkboolean isAbsolute, long time方法阻塞当前线程其中参数isAbsolute等于false且time等于0表示一直阻塞。time大于0表示等待指定的time后阻塞线程会被唤醒这个time是个相对值是个增量值也就是相对当前时间累加time后当前线程就会被唤醒。如果isAbsolute等于true并且time大于0则表示阻塞的线程到指定的时间点后会被唤醒这里time是个绝对时间是将某个时间点换算为ms后的值。另外当其他线程调用了当前阻塞线程的interrupt方法而中断了当前线程时当前线程也会返回而当其他线程调用了unPark方法并且把当前线程作为参数时当前线程也会返回。 void unparkObject thread方法唤醒调用park后阻塞的线程。 下面是JDK8新增的函数这里只列出Long类型操作。 long getAndSetLongObject obj, long offset, long update方法获取对象obj中偏移量为offset的变量volatile语义的当前值并设置变量volatile语义的值为update。 //这个方法只是封装了compareAndSwapLong的使用不需要自己写重试机制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;} long getAndAddLongObject obj, long offset, long addValue方法获取对象obj中偏移量为offset的变量volatile语义的当前值并设置变量值为原始值addValue原理和上面的方法类似。 总结 可以用CAS在无锁的情况下实现原子操作但要明确应用场合非常简单的操作且又不想引入锁可以考虑使用CAS操作当想要非阻塞地完成某一操作也可以考虑CAS。不推荐在复杂操作中引入CAS会使程序可读性变差且难以测试同时会出现ABA问题。 顶尖架构师栈 关注回复关键字 【C01】超10G后端学习面试资源 【IDEA】最新IDEA激活工具和码及教程 【JetBrains软件名】 最新软件激活工具和码及教程 工具码教程 转载于https://mp.weixin.qq.com/s/ELc_9k3QwEHdse_yFatDPw 本文由 mdnice 多平台发布