Home  >  Article  >  Java  >  The synchronized keyword in Java is used to achieve thread synchronization

The synchronized keyword in Java is used to achieve thread synchronization

WBOY
WBOYforward
2023-04-27 14:01:07749browse

1. The expression of synchronized implementation lock

  1. Modify the instance method. For ordinary synchronization methods, the lock is the current instance object

  2. Modify static methods. For static synchronized methods, the lock is the current Class object

  3. Modify method code block. For synchronized method blocks, the lock is the object configured in synchronized brackets!

When a thread attempts to access a synchronized code block, it must obtain the lock. After completion (or an exception occurs), the lock must be released. So where exactly does the lock exist? Let’s explore together!

However, I believe that since everyone can find this article, I believe that everyone is already familiar with its use. We will not go into details about its use and why the data will be confused in multi-threaded situations. explanation of! Only a few ways of using it are listed for reference!

①Modify the instance method

Modify the instance method. For ordinary synchronization methods, the lock is the current instance object

There is no need to say this, use After adding synchronized to the same instance, threads need to be queued to complete an atomic operation, but note that it will only take effect if the same instance is used!

Positive example:

<code>/**<br> * @author huangfu<br> */<br>public class ExploringSynchronized implements Runnable {<br>    /**<br>     * 共享资源(临界资源)<br>     */<br>    static int i=0;<br>    public synchronized void add(){<br>        i++;<br>    }<br><br>    @Override<br>    public void run() {<br>        for (int j = 0; j             add();<br>        }<br>    }<br><br>    public static void main(String[] args) throws InterruptedException {<br>        ExploringSynchronized exploringSynchronized = new ExploringSynchronized();<br>        Thread t1 = new Thread(exploringSynchronized);<br>        Thread t2 = new Thread(exploringSynchronized);<br>        t1.start();<br>        t2.start();<br>        //join 主线程需要等待子线程完成后在结束<br>        t1.join();<br>        t2.join();<br>        System.out.println(i);<br><br>    }<br>}</code>

Counter example:

<code>/**<br> * @author huangfu<br> */<br>public class ExploringSynchronized implements Runnable {<br>    /**<br>     * 共享资源(临界资源)<br>     */<br>    static int i=0;<br>    public synchronized void add(){<br>        i++;<br>    }<br><br>    @Override<br>    public void run() {<br>        for (int j = 0; j             add();<br>        }<br>    }<br><br>    public static void main(String[] args) throws InterruptedException {<br>        Thread t1 = new Thread(new ExploringSynchronized());<br>        Thread t2 = new Thread(new ExploringSynchronized());<br>        t1.start();<br>        t2.start();<br>        //join 主线程需要等待子线程完成后在结束<br>        t1.join();<br>        t2.join();<br>        System.out.println(i);<br><br>    }<br>}</code>

In this case, even if you add synchronized to the method, it will not help, because, for ordinary synchronization methods, the lock is current Instance object! The instance objects are different, so the locks between them are naturally not the same!

②Modify static methods

Modify static methods. For static synchronization methods, the lock is the current Class object

It can be seen from the definition that, His lock is a class object, that is to say, taking the above class as an example: the lock object of the ordinary method is new ExploringSynchronized() and the lock object corresponding to the static method is ExploringSynchronized.classSo if you add a synchronization lock to a static method, even if you re-create an instance, the lock it gets is still the same!

<code>package com.byit.test;<br><br>/**<br> * @author huangfu<br> */<br>public class ExploringSynchronized implements Runnable {<br>    /**<br>     * 共享资源(临界资源)<br>     */<br>    static int i=0;<br>    public synchronized static void add(){<br>        i++;<br>    }<br><br>    @Override<br>    public void run() {<br>        for (int j = 0; j             add();<br>        }<br>    }<br><br>    public static void main(String[] args) throws InterruptedException {<br>        Thread t1 = new Thread(new ExploringSynchronized());<br>        Thread t2 = new Thread(new ExploringSynchronized());<br>        t1.start();<br>        t2.start();<br>        //join 主线程需要等待子线程完成后在结束<br>        t1.join();<br>        t2.join();<br>        System.out.println(i);<br><br>    }<br>}</code>

Of course, the result is what we expected 200000

③Modified method code block

Modified method code block, for synchronized method block, the lock is inside the synchronized brackets Configuration object!

<code>package com.byit.test;<br><br>/**<br> * @author huangfu<br> */<br>public class ExploringSynchronized implements Runnable {<br>    /**<br>     * 锁标记<br>     */<br>    private static final String LOCK_MARK = "LOCK_MARK";<br>    /**<br>     * 共享资源(临界资源)<br>     */<br>    static int i=0;<br>    public void add(){<br>        synchronized (LOCK_MARK){<br>            i++;<br>        }<br>    }<br><br>    @Override<br>    public void run() {<br>        for (int j = 0; j             add();<br>        }<br>    }<br><br>    public static void main(String[] args) throws InterruptedException {<br>        Thread t1 = new Thread(new ExploringSynchronized());<br>        Thread t2 = new Thread(new ExploringSynchronized());<br>        t1.start();<br>        t2.start();<br>        //join 主线程需要等待子线程完成后在结束<br>        t1.join();<br>        t2.join();<br>        System.out.println(i);<br><br>    }<br>}</code>

For synchronized code blocks, what is in the brackets is what the lock object is. You can use this string object and so on!

2. The underlying implementation of synchronized

The implementation of synchronized in java is based on the entry and exit of the Monitor object, whether it is explicit Synchronization (modify the code block, with explicit monitorenter and monitorexit instructions) or implicit synchronization (modify the method body)!

It should be noted that only when modifying the code block, it is implemented based on the monitorenter and monitorexit instructions; when modifying the method, it is implemented through another Achieved in a way! I will talk about it later!

Before understanding the bottom layer of the entire implementation, I still hope that you can have a general understanding of the structure details of objects in memory!

The synchronized keyword in Java is used to achieve thread synchronization

  • Instance variable: stores the attribute data information of the class, including the attribute information of the parent class. If it is the instance part of the array, it also includes the length of the array. This part of memory is aligned by 4 bytes.

  • Filling data: Because the virtual machine requires that the object starting address must be an integer multiple of 8 bytes. The padding data does not have to exist, it is just for byte alignment, just understand this.

We can simply understand these two concepts! We are not going to explore the composition principles of objects today! Let's focus on exploring the object header, which is particularly important for our understanding of locks!

Generally speaking, the lock used by synchronized exists in the object header! If it is an array object, the virtual machine uses 3 words wide to store the object. If it is a non-array object, it uses two words wide to store the object header! In the word virtual machine, 1 word width is equal to 4 bytes! The main structure is composed of Mark Word and Class Metadata Address. The structure is as follows:

##Virtual machine digitsHeader object structureDescription##32/64bit32/64bit32/64bit (array)

It can be seen from the above table that lock information exists in Mark Word, so how is Mark Word composed?

Mark Word Storage The object’s hashCode, lock information or generation age or GC flag and other information
Class Metadata Address is stored in the formation type Data pointer
Aarray length The length of the array
Lock status 25bit 4bit 1bit is a biased lock 2bit lock flag
Unlocked status Hashcode of the object Generation age of the object 0 01

在运行起见,mark Word 里存储的数据会随着锁的标志位的变化而变化。mark Word可能变化为存储一下四种数据

The synchronized keyword in Java is used to achieve thread synchronization

Java SE 1.6为了减少获得锁和释放锁带来的消耗,引入了偏向锁轻量级锁,从之前上来就是重量级锁到1.6之后,锁膨胀升级的优化,极大地提高了synchronized的效率;

锁一共有4中状态,级别从低到高:

The synchronized keyword in Java is used to achieve thread synchronization

这几个状态会随着锁的竞争,逐渐升级。锁可以升级,但是不能降级,其根本的原因就是为了提高获取锁和释放锁的效率!

那么,synchronized是又如何保证的线程安全的呢?或许我们需要从字节码寻找答案!

<code>package com.byit.test;<br><br>/**<br> * @author Administrator<br> */<br>public class SynText {<br>    private static String A = "a";<br>    public int i ;<br><br>    public void add(){<br>        synchronized (A){<br>            i++;<br>        }<br><br>    }<br>}</code>

反编译的字节码

<code>Compiled from "SynText.java"<br>public class com.byit.test.SynText {<br>  public int i;<br><br>  public com.byit.test.SynText();<br>    Code:<br>       0: aload_0<br>       1: invokespecial #1                  // Method java/lang/Object."<init>":()V<br>       4: return<br><br>  public void add();<br>    Code:<br>       0: getstatic     #2                  // Field A:Ljava/lang/String;<br>       3: dup<br>       4: astore_1<br>       5: monitorenter<br>       6: aload_0<br>       7: dup<br>       8: getfield      #3                  // Field i:I<br>      11: iconst_1<br>      12: iadd<br>      13: putfield      #3                  // Field i:I<br>      16: aload_1<br>      17: monitorexit<br>      18: goto          26<br>      21: astore_2<br>      22: aload_1<br>      23: monitorexit<br>      24: aload_2<br>      25: athrow<br>      26: return<br>    Exception table:<br>       from    to  target type<br>           6    18    21   any<br>          21    24    21   any<br><br>  static {};<br>    Code:<br>       0: ldc           #4                  // String a<br>       2: putstatic     #2                  // Field A:Ljava/lang/String;<br>       5: return<br>}<br></init></code>

省去不必要的,简化在简化

<code>   5: monitorenter<br>      ...<br>      17: monitorexit<br>      ...<br>      23: monitorexit</code>

从字节码中可知同步语句块的实现使用的是monitorentermonitorexit指令,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置,当执行monitorenter指令的时候,线程将试图获取对象所所对应的monitor特权,当monitor的的计数器为0的时候,线程就可以获取monitor,并将计数器设置为1.去锁成功!如果当前线程已经拥有monitor特权,则可以直接进入方法(可重入锁),计数器+1;如果其他线程已经拥有了monitor特权,那么本县城将会阻塞!

拥有monitor特权的线程执行完成后释放monitor,并将计数器设置为0;同时执行monitorexit指令;不要担心出现异常无法执行monitorexit指令;为了保证在方法异常完成时 monitorenter 和 monitorexit 指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有的异常,它的目的就是用来执行 monitorexit 指令。从字节码中也可以看出多了一个monitorexit指令,它就是异常结束时被执行的释放monitor 的指令。

同步代码块的原理了解了,那么同步方法如何解释?不急,我们不妨来反编译一下同步方法的状态!

javap -verbose -p SynText > 3.txt

代码

<code>package com.byit.test;<br><br>/**<br> * @author huangfu<br> */<br>public class SynText {<br>    public int i ;<br><br>    public synchronized void add(){<br>        i++;<br><br>    }<br>}</code>

字节码

<code>Classfile /D:/2020project/byit-myth-job/demo-client/byit-demo-client/target/classes/com/byit/test/SynText.class<br>  Last modified 2020-1-6; size 382 bytes<br>  MD5 checksum e06926a20f28772b8377a940b0a4984f<br>  Compiled from "SynText.java"<br>public class com.byit.test.SynText<br>  minor version: 0<br>  major version: 52<br>  flags: ACC_PUBLIC, ACC_SUPER<br>Constant pool:<br>   #1 = Methodref          #4.#17         // java/lang/Object."<init>":()V<br>   #2 = Fieldref           #3.#18         // com/byit/test/SynText.i:I<br>   #3 = Class              #19            // com/byit/test/SynText<br>   #4 = Class              #20            // java/lang/Object<br>   #5 = Utf8               i<br>   #6 = Utf8               I<br>   #7 = Utf8               <init><br>   #8 = Utf8               ()V<br>   #9 = Utf8               Code<br>  #10 = Utf8               LineNumberTable<br>  #11 = Utf8               LocalVariableTable<br>  #12 = Utf8               this<br>  #13 = Utf8               Lcom/byit/test/SynText;<br>  #14 = Utf8               syncTask<br>  #15 = Utf8               SourceFile<br>  #16 = Utf8               SynText.java<br>  #17 = NameAndType        #7:#8          // "<init>":()V<br>  #18 = NameAndType        #5:#6          // i:I<br>  #19 = Utf8               com/byit/test/SynText<br>  #20 = Utf8               java/lang/Object<br>{<br>  public int i;<br>    descriptor: I<br>    flags: ACC_PUBLIC<br><br>  public com.byit.test.SynText();<br>    descriptor: ()V<br>    flags: ACC_PUBLIC<br>    Code:<br>      stack=1, locals=1, args_size=1<br>         0: aload_0<br>         1: invokespecial #1                  // Method java/lang/Object."<init>":()V<br>         4: return<br>      LineNumberTable:<br>        line 6: 0<br>      LocalVariableTable:<br>        Start  Length  Slot  Name   Signature<br>            0       5     0  this   Lcom/byit/test/SynText;<br><br>  public synchronized void syncTask();<br>    descriptor: ()V<br>    flags: ACC_PUBLIC, ACC_SYNCHRONIZED<br>    Code:<br>      stack=3, locals=1, args_size=1<br>         0: aload_0<br>         1: dup<br>         2: getfield      #2                  // Field i:I<br>         5: iconst_1<br>         6: iadd<br>         7: putfield      #2                  // Field i:I<br>        10: return<br>      LineNumberTable:<br>        line 10: 0<br>        line 11: 10<br>      LocalVariableTable:<br>        Start  Length  Slot  Name   Signature<br>            0      11     0  this   Lcom/byit/test/SynText;<br>}<br>SourceFile: "SynText.java"<br></init></init></init></init></code>

简化,在简化

<code> public synchronized void syncTask();<br>    descriptor: ()V<br>    flags: ACC_PUBLIC, ACC_SYNCHRONIZED<br>    Code:<br>      stack=3, locals=1, args_size=1<br>         0: aload_0<br>         1: dup</code>

我们能够看到 flags: ACC_PUBLIC, ACC_SYNCHRONIZED这样的一句话

从字节码中可以看出,synchronized修饰的方法并没有monitorenter指令和monitorexit指令,取得代之的确实是ACC_SYNCHRONIZED标识,该标识指明了该方法是一个同步方法,JVM通过该ACC_SYNCHRONIZED访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。这便是synchronized锁在同步代码块和同步方法上实现的基本原理。

那么在JAVA6之前,为什么synchronized会如此的慢?

那是因为,操作系统实现线程之间的切换需要系统内核从用户态切换到核心态!这个状态之间的转换,需要较长的时间,时间成本高!所以这也就是synchronized慢的原因!

三、锁膨胀的过程

在这之前,你需要知道什么是锁膨胀!他是JAVA6之后新增的一个概念!是一种针对之前重量级锁的一种性能的优化!他的优化,大部分是基于经验上的一些感官,对锁来进行优化!

①偏向锁

研究发现,大多数情况下,锁不仅不存在多线程竞争,而且还总是由一条线程获得!因为为了减少锁申请的次数!引进了偏向锁!在没有锁竞争的情况下,如果一个线程获取到了锁,那么锁就进入偏向锁的模式!当线程再一次请求锁时,无需申请,直接获取锁,进入方法!但是前提是没有锁竞争的情况,存在锁竞争,锁会立即膨胀,膨胀为轻量级锁!

②轻量级锁

偏向锁失败,那么锁膨胀为轻量级锁!此时锁机构变为轻量级锁结构!他的经验依据是:“绝大多数情况下,在整个同步周期内,不会存在锁的竞争”,故而,轻量级锁适合,线程交替进行的场景!如果在同一时间出现两条线程对同一把锁的竞争,那么此时轻量级锁就不会生效了!但是,jdk官方为了是锁的优化性能更好,轻量级锁失效后,并不会立即膨胀为重量级锁!而是将锁转换为自旋锁状态!

③自旋锁

轻量级锁失败后,为了是避免线程挂起,引起内核态的切换!为了优化,此时线程会进入自选状态!他可能会进行几十次,上百次的空轮训!为什么呢?又是经验之谈!他们认为,大多数情况下,线程持有锁的时间都不会太长!做几次空轮训,就能大概率的等待到锁!事实证明,这种优化方式确实有效!最后如果实在等不到锁!没办法,才会彻底升级为重量级锁!

④锁消除

jvm在进行代码编译时,会基于上下文扫描;将一些不可能存在资源竞争的的锁给消除掉!这也是JVM对于锁的一种优化方式!不得不感叹,jdk官方的脑子!举个例子!在方法体类的局部变量对象,他永远也不可能会发生锁竞争,例如:

<code>/**<br> * @author huangfu<br> */<br>public class SynText {<br>    public static void add(String name1 ,String name2){<br>        StringBuffer sb = new StringBuffer();<br>        sb.append(name1).append(name2);<br>    }<br><br>    public static void main(String[] args) {<br>        for (int i = 0; i             add("w"+i,"q"+i);<br>        }<br>    }<br>}</code>

不能否认,StringBuffer是线程安全的!但是他永远也不会被其他线程引用!故而,锁失效!故而,被消除掉!

The above is the detailed content of The synchronized keyword in Java is used to achieve thread synchronization. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:yisu.com. If there is any infringement, please contact admin@php.cn delete