• (锁) 系列篇 -- "1.synchronized实现以及与cas区别"


    (锁) 系列篇

    1、synchronized实现

    (1)synchronized实现同步的基础:Java中的每一个对象都可以作为锁。具体表现为以下3种形式:

    • 对于普通同步方法,锁是当前实例对象。
    public class SynchronizedTest {
        // ......
        public synchronized void test1(){
        }
    }
    • 对于静态同步方法,锁是当前类的Class对象。
    public class SynchronizedTest { 
         // ......
        public static synchronized void test2(){
        }
    }
    • 对于同步方法块,锁是Synchonized括号里配置的对象。
    public class SynchronizedTest {   
        // ......
        Object lock = new Object() ;
        public void test3(){
            synchronized (lock){
            }
        }
    }

    (2)对于SynchronizedTest.java使用javap反解析出汇编指令(open-jdk 1.8.0):

    javac SynchronizedTest.java

    javap -v SynchronizedTest.class

    // ..............
    { //.......
    public synchronized void test1(); descriptor: ()V flags: ACC_PUBLIC, ACC_SYNCHRONIZED Code: stack=0, locals=1, args_size=1 0: return LineNumberTable: line 7: 0 public static synchronized void test2(); descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED Code: stack=0, locals=0, args_size=0 0: return LineNumberTable: line 11: 0 public void test3(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=1 0: aload_0 1: getfield #3 // Field lock:Ljava/lang/Object; 4: dup 5: astore_1 6: monitorenter 7: aload_1 8: monitorexit 9: goto 17 12: astore_2 13: aload_1 14: monitorexit 15: aload_2 16: athrow 17: return Exception table: from to target type 7 9 12 any 12 15 12 any LineNumberTable: line 15: 0 line 17: 7 line 18: 17 StackMapTable: number_of_entries = 2 frame_type = 255 /* full_frame */ offset_delta = 12 locals = [ class com/nancy/sync/SynchronizedTest, class java/lang/Object ] stack = [ class java/lang/Throwable ] frame_type = 250 /* chop */ offset_delta = 4 } SourceFile: "SynchronizedTest.java"

    由此看出同步方法使用ACC_SYNCHRONIZED标识进入该方法需要同步,同块代码块使用monitorenter / monitorexit来标识同步代码块保护区。虽然实现的形式不一致,实质都是使用moniter对象头Mark Word的标志位实现。

    (3)synchronized特点

    • 重量锁高并发场景下效率低下(早期依赖于底层的操作系统的Mutex Lock来实现,需要内核态和用户态之间转换)
    • 保证线程对变量访问的可见性、排他性、原子性
       可见性:修改变量保证其他线程可见
      排他性:同一时刻只能一个线程访问
      原子性:对变量的操作保证原子执行,例如:i++就是经典的读改写操作
    2、cas(Compare And Swap)

    (1)cas实现

    cas类似乐观锁,利用操作系统指令原子执行操作,一般结合自旋实现。例如AtomicInteger#getAndIncrement方法:

    public class AtomicInteger extends Number implements java.io.Serializable {
        private static final long serialVersionUID = 6214790243416807050L;
    
    
        // setup to use Unsafe.compareAndSwapInt for updates
        private static final Unsafe unsafe = Unsafe.getUnsafe();
        private static final long valueOffset;
    
        static {
            try {
                valueOffset = unsafe.objectFieldOffset
                    (AtomicInteger.class.getDeclaredField("value"));
            } catch (Exception ex) { throw new Error(ex); }
        }
        private volatile int value;
    
        public final int getAndIncrement() {
            return unsafe.getAndAddInt(this, valueOffset, 1);
        }
        // .....
    }
    
    public final class Unsafe {
      public final int getAndAddInt(Object o, long offset, int delta) {
            int v;
            do {
                v = getIntVolatile(o, offset);
            } while (!compareAndSwapInt(o, offset, v, v + delta));
            return v;
        }
    
       /** Volatile version of {@link #getInt(Object, long)}  */
        public native int     getIntVolatile(Object o, long offset);
    
      /**
         * Atomically update Java variable to <tt>x</tt> if it is currently
         * holding <tt>expected</tt>.
         * @return <tt>true</tt> if successful
         */
        public final native boolean compareAndSwapInt(Object o, long offset,
                                                      int expected,
                                                      int x);
        
       // .........
    }

     通过不断自旋实现原子更新操作,可想高并发场景下虽去掉了锁的概念,但过多无效空旋也会占用CPU时间。

    (2)cas实现副作用:可能引发典型"A-B-A问题": 

    cas执行操作时会检查该值与期望值A是否一致(即检查内存中某个地址变量的值),如果一致则更新为C。但如果值从原来是A,变成B,又变成了A,

    那么cas进行检查时不会发现它的值发生变化,而实际已经发生了变化(1A-2B-3A),即cas的"A-B-A问题"。

    解决方式即使用版本号或者时间戳,发现不一致即更新失败。例如:AtomicStampedReference引入stamp时间戳解决"A-B-A问题":

    /**
     * An {@code AtomicStampedReference} maintains an object reference
     * along with an integer "stamp", that can be updated atomically.
     *
     * <p>Implementation note: This implementation maintains stamped
     * references by creating internal objects representing "boxed"
     * [reference, integer] pairs.
     *
     * @since 1.5
     * @author Doug Lea
     * @param <V> The type of object referred to by this reference
     */
    public class AtomicStampedReference<V> {
        private static class Pair<T> {
            final T reference;
            final int stamp;
            private Pair(T reference, int stamp) {
                this.reference = reference;
                this.stamp = stamp;
            }
            static <T> Pair<T> of(T reference, int stamp) {
                return new Pair<T>(reference, stamp);
            }
        }
    
        private volatile Pair<V> pair;
    
        // ......
        public boolean compareAndSet(V   expectedReference,
                                     V   newReference,
                                     int expectedStamp,
                                     int newStamp) {
            Pair<V> current = pair;
            return
                expectedReference == current.reference &&
                expectedStamp == current.stamp &&
                ((newReference == current.reference &&
                  newStamp == current.stamp) ||
                 casPair(current, Pair.of(newReference, newStamp)));
        }
    
        // Unsafe mechanics
    
        private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe();
        private static final long pairOffset =
            objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class);
    
        private boolean casPair(Pair<V> cmp, Pair<V> val) {
            return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
        }
    
        static long objectFieldOffset(sun.misc.Unsafe UNSAFE,
                                      String field, Class<?> klazz) {
            try {
                return UNSAFE.objectFieldOffset(klazz.getDeclaredField(field));
            } catch (NoSuchFieldException e) {
                // Convert Exception to corresponding Error
                NoSuchFieldError error = new NoSuchFieldError(field);
                error.initCause(e);
                throw error;
            }
        }
    }
    AtomicStampedReference.java
    3、synchronized和cas二者区别
    • synchronized为锁机制隐含着获锁和解锁的操作。利用操作系统提供支持实现访问竞争资源的顺序性,早期JDK版本未对synchronized优化并发效率低。后期引入轻量级锁、偏向锁等概念,大大优化性能。优势在于使用简单,维护成本低。
    • cas实现了lock-free的概念,不引入锁也可解决并发场景问题。如果单纯使用cas会引起多重副作用,高级并发包下配合AQS一起可提升并发效率。
    • 如果单独使用,二者都偏向并发量不高或者业务简单的场景。例如:单体web应用单调计数场景。
  • 相关阅读:
    css3发光闪烁的效果
    移动端滚动加载数据实现
    JS生成一个简单的验证码
    百度地图在IOS中不显示
    vue开发神奇vue-devtools的安装
    gulp搭建服务
    webstorm中配置ES6语法
    centos 7 中防火墙的关闭问题
    centos命令
    Cesium加载影像
  • 原文地址:https://www.cnblogs.com/xiaoxing/p/12552239.html
Copyright © 2020-2023  润新知