做购物网站的公司,泉州网页搜索排名提升,网站规划和建设方案,怎样做音乐网站本文转载于SegmentFault社区作者#xff1a;ytaosynchronized是 Java 编程中的一个重要的关键字#xff0c;也是多线程编程中不可或缺的一员。本文就对它的使用和锁的一些重要概念进行分析。使用及原理synchronized 是一个重量级锁#xff0c;它主要实现同步操作#xff0c… 本文转载于SegmentFault社区作者ytaosynchronized是 Java 编程中的一个重要的关键字也是多线程编程中不可或缺的一员。本文就对它的使用和锁的一些重要概念进行分析。使用及原理synchronized 是一个重量级锁它主要实现同步操作在 Java 对象锁中有三种使用方式普通方法中使用锁是当前实例对象。静态方法中使用锁是当前类的对象。代码块中使用锁是代码代码块中配置的对象。使用在代码中使用方法分别如下普通方法使用/** * 公众号ytao * 博客https://ytao.top */public class SynchronizedMethodDemo{ public synchronized void demo(){ // ...... }}静态方法使用/** * 公众号ytao * 博客https://ytao.top */public class SynchronizedMethodDemo{ public synchronized static void staticDemo(){ // ...... }}代码块中使用/** * 公众号ytao * 博客https://ytao.top */public class SynchronizedDemo{ public void demo(){ synchronized (SynchronizedDemo.class){ // ...... } }}实现原理方法和代码块的实现原理使用不同方式代码块每个对象都拥有一个monitor对象代码块的{}中会插入monitorenter和monitorexit指令。当执行monitorenter指令时会进入monitor对象获取锁当执行monitorexit命令时会退出monitor对象释放锁。同一时刻只能有一个线程进入在monitorenter中。先将SynchronizedDemo.java使用javac SynchronizedDemo.java命令将其编译成SynchronizedDemo.class。然后使用javap -c SynchronizedDemo.class反编译字节码。Compiled from SynchronizedDemo.javapublic class SynchronizedDemo { public SynchronizedDemo(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object.:()V 4: return public void demo(); Code: 0: ldc #2 // class SynchronizedDemo 2: dup 3: astore_1 4: monitorenter // 进入 monitor 5: aload_1 6: monitorexit // 退出 monitor 7: goto 15 10: astore_2 11: aload_1 12: monitorexit // 退出 monitor 13: aload_2 14: athrow 15: return Exception table: from to target type 5 7 10 any 10 13 10 any}上面反编码后的代码有两个monitorexit指令一个插入在异常位置一个插入在方法结束位置。方法方法中的synchronized与代码块中实现的方式不同方法中会添加一个叫ACC_SYNCHRONIZED的标志当调用方法时首先会检查是否有ACC_SYNCHRONIZED标志如果存在则获取monitor对象调用monitorenter和monitorexit指令。通过javap -v -c SynchronizedMethodDemo.class命令反编译SynchronizedMethodDemo类。-v参数即-verbose表示输出反编译的附加信息。下面以反编译普通方法为例。Classfile /E:/SynchronizedMethodDemo.class Last modified 2020-6-28; size 381 bytes MD5 checksum 55ca2bbd9b6939bbd515c3ad9e59d10c Compiled from SynchronizedMethodDemo.javapublic class SynchronizedMethodDemo minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPERConstant pool: #1 Methodref #5.#13 // java/lang/Object.:()V #2 Fieldref #14.#15 // java/lang/System.out:Ljava/io/PrintStream; #3 Methodref #16.#17 // java/io/PrintStream.println:()V #4 Class #18 // SynchronizedMethodDemo #5 Class #19 // java/lang/Object #6 Utf8 #7 Utf8 ()V #8 Utf8 Code #9 Utf8 LineNumberTable #10 Utf8 demo #11 Utf8 SourceFile #12 Utf8 SynchronizedMethodDemo.java #13 NameAndType #6:#7 // :()V #14 Class #20 // java/lang/System #15 NameAndType #21:#22 // out:Ljava/io/PrintStream; #16 Class #23 // java/io/PrintStream #17 NameAndType #24:#7 // println:()V #18 Utf8 SynchronizedMethodDemo #19 Utf8 java/lang/Object #20 Utf8 java/lang/System #21 Utf8 out #22 Utf8 Ljava/io/PrintStream; #23 Utf8 java/io/PrintStream #24 Utf8 println{ public SynchronizedMethodDemo(); descriptor: ()V flags: ACC_PUBLIC Code: stack1, locals1, args_size1 0: aload_0 1: invokespecial #1 // Method java/lang/Object.:()V 4: return LineNumberTable: line 5: 0 public synchronized void demo(); descriptor: ()V flags: ACC_PUBLIC, ACC_SYNCHRONIZED // ACC_SYNCHRONIZED 标志 Code: stack1, locals1, args_size1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: invokevirtual #3 // Method java/io/PrintStream.println:()V 6: return LineNumberTable: line 8: 0 line 10: 6}SourceFile: SynchronizedMethodDemo.java上面对代码块和方法的实现方式进行探究代码块通过在编译后的代码中添加monitorenter和monitorexit指令。方法中通过添加ACC_SYNCHRONIZED标志来决定是否调用monitor对象。Java 对象头synchronized锁的相关数据存放在 Java 对象头中。Java 对象头指的 HotSpot 虚拟机的对象头使用2个字宽或3个字宽存储对象头。第一部分存储运行时的数据hashCode、锁标记位、是否偏向锁、GC分代年龄等等信息称作为Mark Word。第二部分存储对象类型数据的指针。第三部分如果对象是数组的话则用这部分来存储数组长度。Java 对象头 Mark Word 存储内容锁升级synchronized 称为重量级锁但 Java SE 1.6 为优化该锁的性能而减少获取和释放锁的性能消耗引入偏向锁和轻量级锁。锁的高低级别为无锁→偏向锁→轻量级锁→重量级锁。其中锁的升级是不可逆的只能由低往高级别升不能由高往低降。偏向锁偏向锁是优化在无多线程竞争情况下提高程序的的运行性能而使用到的锁。在Mark Word中存储一个值用来标志是否为偏向锁在 32 位虚拟机和 64 位虚拟机中都是使用一个字节存储0 为非偏向锁1 为是偏向锁。当第一次被线程获取偏向锁时会将Mark Word中的偏向锁标志设置为 1同时使用 CAS 操作来记录这个线程的ID。获取到偏向锁的线程再次进入获取锁时只需判断Mark Word是否存储着当前线程ID如果是则不需再次进行获取锁操作而是直接持有该锁。撤销锁如果有其他线程出现尝试获取偏向锁让偏向锁处于竞争状态那么当前偏向锁就会撤销。撤销偏向锁时首先会暂停持有偏向锁的线程并将线程ID设为空然后检查该线程是否存活当暂停线程非存活则设置对象头为无锁状态。当暂停线程存活执行偏向锁的栈最后对象头的保存其他获取到偏向锁的线程ID或者转向无锁状态。当确定代码一定执行在多线程访问中时那么这时的偏向锁是无法发挥到优势如果继续使用偏向锁就显得过于累赘给系统带来不必要的性能开销此时可以设置 JVM 参数-XX:BiasedLockingfalse来关闭偏向锁。轻量级锁代码进入同步块的时候如果对象头不是锁定状态JVM 则会在当前线程的栈桢中创建一个锁记录的空间将锁对象头的Mark Word复制一份到锁记录中这份复制过来的Mark Word叫做Displaced Mark Word。然后使用 CAS 操作将锁对象头中的Mark Word更新为指向锁记录的指针。如果更新成功当前线程则会获得锁如果失败JVM 先检查锁对象的Mark Word是否指向当前线程是指向当前线程的话则当前线程已持有锁否则存在多线程竞争当前线程会通过自旋获取锁这里的自旋可以理解为循环尝试获取锁所以这过程是消耗 CPU 的过程。当轻量级锁存在竞争状态并自旋获取轻量级锁失败时轻量级锁就会膨胀为重量级锁锁对象的Mark Word会更新为指向重量级锁的指针等待获取锁的线程进入阻塞状态。解锁轻量级锁解锁是使用 CAS 操作将锁记录替换到Mark Word中如果替换成功则表示同步操作已完成。如果失败则表示其他竞争线程尝试过获取该轻量级锁需要在释放锁的同时去唤醒其他被阻塞的线程被唤醒的线程回去再次去竞争锁。总结通过分析synchronized的使用以及 Java SE 1.6 升级优化锁后的设计可以看出其主要是解决是通过多加入两级相对更轻巧的偏向锁和轻量级锁来优化重量级锁的性能消耗但是这并不是一定会起到优化作用主要是解决大多数情况下不存在多线程竞争以及同一线程多次获取锁的的优化这也是根据平时在编码中多观察多反思得出的权衡方案。推荐阅读《volatile 手摸手带你解析》https://ytao.top/2020/03/15/18-volatile/《Java 线程通信之 wait/notify 机制》https://ytao.top/2020/05/12/24-thread-wait-notify/《Java 多线程中使用 JDK 自带工具类实现计数器》https://ytao.top/2020/05/17/25-thread-count/《Java 线程基础从这篇开始》https://ytao.top/2020/04/19/22-thread-base/- END -