Synchronized 的用法及底层原理

2021-05-16  本文已影响0人  overflowedstack
一. Synchronized的用法

Synchronized是Java里的一个关键字。

1. 可以用来修饰方法。那么同时只能有一个线程进入某个对象的这个方法。

public synchronized void syncMethod() {}

例如以下这个方法中,两个线程就是互斥的。第二个线程需要等第一个线程执行结束。

package demo.multithread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SyncLock {

    public static void main(String[] args) {
        Person p1 = new Person(20);
        
        ExecutorService pool = Executors.newFixedThreadPool(5);
        pool.submit(new Runnable() {

            @Override
            public void run() {
                p1.testSync();
            }});
        pool.submit(new Runnable() {

            @Override
            public void run() {
                p1.testSync();
            }});
        pool.shutdown();
    }
}

class Person {
    public Integer age;
    
    Person(Integer age) {
        this.setAge(age);
    }
    public synchronized void testSync() {
        System.out.println("step into testSync");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("step out testSync");
    }
    
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
}
2. 修饰静态方法。那么同时只能有一个线程进入这个class的此方法。

public synchronized static void syncMethod() {}

例如下面这个例子中,同一个class的两个对象。两个线程同时,各自访问一个对象的同步方法,是互斥的。

package demo.multithread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SyncLock {

    public static void main(String[] args) {
        Person p1 = new Person(20);
        Person p2 = new Person(30);
        
        ExecutorService pool = Executors.newFixedThreadPool(5);
        pool.submit(new Runnable() {

            @Override
            public void run() {
                p1.testSync();
            }});
        pool.submit(new Runnable() {

            @Override
            public void run() {
                p2.testSync();
            }});
        pool.shutdown();
    }
}

class Person {
    public Integer age;
    
    Person(Integer age) {
        this.setAge(age);
    }
    public synchronized static void testSync() {
        System.out.println("step into testSync");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("step out testSync");
    }
    
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
}
3. 修饰代码块,synchronized后面括号里是一个变量。那么同时只能有一个线程获取这个变量。

synchronized(a1) {}

例如下面这个例子,两个线程在竞争同一个变量p1.age

package demo.multithread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SyncLock {

    public static void main(String[] args) {
        Person p1 = new Person(20);
        
        ExecutorService pool = Executors.newFixedThreadPool(5);
        pool.submit(new Runnable() {

            @Override
            public void run() {
                synchronized(p1.age) {
                    System.out.println("read age!");
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("complete!");
                }
            }});
        pool.submit(new Runnable() {

            @Override
            public void run() {
                //p2.testSync();
                synchronized(p1.age) {
                    System.out.println("read age!");
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("complete!");
                }
            }});
        pool.shutdown();
    }
}

class Person {
    public Integer age;
    
    Person(Integer age) {
        this.setAge(age);
    }
    
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
}
4. 修饰代码块,synchronized后面括号里是this。那么一个线程访问一个对象中的该代码块时,其他线程会被阻塞。

synchronized(this) {}

在下面这个例子里,两个线程不能同时访问同一个对象的同步方法或者代码块。

package demo.multithread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SyncLock {

    public static void main(String[] args) {
        Person p1 = new Person(20);
        
        ExecutorService pool = Executors.newFixedThreadPool(5);
        pool.submit(new Runnable() {

            @Override
            public void run() {
                p1.testSync();
            }});
        pool.submit(new Runnable() {

            @Override
            public void run() {
                p1.testSync2();
            }});
        pool.shutdown();
    }
}

class Person {
    public Integer age;
    
    Person(Integer age) {
        this.setAge(age);
    }
    
    public void testSync() {
        synchronized(this) {
            System.out.println("step into testSync");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("step out testSync");
        }
    }
    
    public synchronized void testSync2() {
        System.out.println("step into testSync2");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("step out testSync2");
    }
    
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
}
5. 修饰代码块,synchronized后面括号里是class:

synchronized(Person.class) {}

看这个例子,同一个class的不同对象,不能同时访问synchronized(Person.class)修饰的同步代码块。

package demo.multithread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SyncLock {

    public static void main(String[] args) {
        Person p1 = new Person(20);
        Person p2 = new Person(30);
        
        ExecutorService pool = Executors.newFixedThreadPool(5);
        pool.submit(new Runnable() {

            @Override
            public void run() {
                p1.testSync();
            }});
        pool.submit(new Runnable() {

            @Override
            public void run() {
                p2.testSync2();
            }});
        pool.shutdown();
    }
}

class Person {
    public Integer age;
    
    Person(Integer age) {
        this.setAge(age);
    }
    
    public void testSync() {
        synchronized(Person.class) {
            System.out.println("step into testSync");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("step out testSync");
        }
    }
    
    public void testSync2() {
        synchronized(Person.class) {
            System.out.println("step into testSync2");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("step out testSync2");
        }
    }
    
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
}
二. Synchronized的原理

synchronized是怎样实现这种同步的效果呢?

1. 首先,java class的每个对象都有个Monitor对象。

http://hg.openjdk.java.net/jdk8/jdk8/hotspot/ 上下载JVM源码,目录src/share/vm/runtime里面有个objectMonitor class。这个monitor class里记录了,当前是哪个线程拥有该monitor,以及线程的重入次数。

  // initialize the monitor, exception the semaphore, all other fields
  // are simple integers or pointers
  ObjectMonitor() {
    _header       = NULL;
    _count        = 0;
    _waiters      = 0,
    _recursions   = 0;  // 线程的重入次数
    _object       = NULL;  //
    _owner        = NULL;  //标识拥有该monitor的线程
    _WaitSet      = NULL; //处于wait状态的线程,会被加到这里
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; //处于等待锁,block状态的线程,加到这个list
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }
2. 再来看看编译成后的class文件.
package demo.multithread;

public class Sync1 {
    public static void main(String[] args) {
        String name = "Tom";
        synchronized(name) {
            System.out.print(name + " hello");
        }
    }
}
$ javap -v Sync1.class 
Classfile Sync1.class
  Last modified 2021-5-15; size 959 bytes
  MD5 checksum f62167984791ee72e37d981232b44217
  Compiled from "Sync1.java"
public class demo.multithread.Sync1
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Class              #2             // demo/multithread/Sync1
   #2 = Utf8               demo/multithread/Sync1
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Methodref          #3.#9          // java/lang/Object."<init>":()V
   #9 = NameAndType        #5:#6          // "<init>":()V
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Ldemo/multithread/Sync1;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = String             #17            // Tom
  #17 = Utf8               Tom
  #18 = Fieldref           #19.#21        // java/lang/System.out:Ljava/io/PrintStream;
  #19 = Class              #20            // java/lang/System
  #20 = Utf8               java/lang/System
  #21 = NameAndType        #22:#23        // out:Ljava/io/PrintStream;
  #22 = Utf8               out
  #23 = Utf8               Ljava/io/PrintStream;
  #24 = Class              #25            // java/lang/StringBuilder
  #25 = Utf8               java/lang/StringBuilder
  #26 = Methodref          #27.#29        // java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
  #27 = Class              #28            // java/lang/String
  #28 = Utf8               java/lang/String
  #29 = NameAndType        #30:#31        // valueOf:(Ljava/lang/Object;)Ljava/lang/String;
  #30 = Utf8               valueOf
  #31 = Utf8               (Ljava/lang/Object;)Ljava/lang/String;
  #32 = Methodref          #24.#33        // java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
  #33 = NameAndType        #5:#34         // "<init>":(Ljava/lang/String;)V
  #34 = Utf8               (Ljava/lang/String;)V
  #35 = String             #36            //  hello
  #36 = Utf8                hello
  #37 = Methodref          #24.#38        // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #38 = NameAndType        #39:#40        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #39 = Utf8               append
  #40 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #41 = Methodref          #24.#42        // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #42 = NameAndType        #43:#44        // toString:()Ljava/lang/String;
  #43 = Utf8               toString
  #44 = Utf8               ()Ljava/lang/String;
  #45 = Methodref          #46.#48        // java/io/PrintStream.print:(Ljava/lang/String;)V
  #46 = Class              #47            // java/io/PrintStream
  #47 = Utf8               java/io/PrintStream
  #48 = NameAndType        #49:#34        // print:(Ljava/lang/String;)V
  #49 = Utf8               print
  #50 = Utf8               args
  #51 = Utf8               [Ljava/lang/String;
  #52 = Utf8               name
  #53 = Utf8               Ljava/lang/String;
  #54 = Utf8               StackMapTable
  #55 = Class              #51            // "[Ljava/lang/String;"
  #56 = Class              #57            // java/lang/Throwable
  #57 = Utf8               java/lang/Throwable
  #58 = Utf8               SourceFile
  #59 = Utf8               Sync1.java
{
  public demo.multithread.Sync1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #8                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Ldemo/multithread/Sync1;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=3, args_size=1
         0: ldc           #16                 // String Tom
         2: astore_1
         3: aload_1
         4: dup
         5: astore_2
         6: monitorenter
         7: getstatic     #18                 // Field java/lang/System.out:Ljava/io/PrintStream;
        10: new           #24                 // class java/lang/StringBuilder
        13: dup
        14: aload_1
        15: invokestatic  #26                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
        18: invokespecial #32                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
        21: ldc           #35                 // String  hello
        23: invokevirtual #37                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        26: invokevirtual #41                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        29: invokevirtual #45                 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
        32: aload_2
        33: monitorexit
        34: goto          40
        37: aload_2
        38: monitorexit
        39: athrow
        40: return
      Exception table:
         from    to  target type
             7    34    37   any
            37    39    37   any
      LineNumberTable:
        line 5: 0
        line 6: 3
        line 7: 7
        line 6: 32
        line 9: 40
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      41     0  args   [Ljava/lang/String;
            3      38     1  name   Ljava/lang/String;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 37
          locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 2
}
SourceFile: "Sync1.java"
package demo.multithread;

public class Sync2 {
    public synchronized void testSync2() {
        System.out.println("helloworld");
    }
}
$ javap -v Sync2.class 
Classfile Sync2.class
  Last modified 2021-5-15; size 411 bytes
  MD5 checksum d73c40e82c4502c5d72c37692168a702
  Compiled from "Sync2.java"
public class demo.multithread.Sync2
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#14         // java/lang/Object."<init>":()V
   #2 = Fieldref           #15.#16        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #17            // helloworld
   #4 = Methodref          #18.#19        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #20            // demo/multithread/Sync2
   #6 = Class              #21            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               testSync2
  #12 = Utf8               SourceFile
  #13 = Utf8               Sync2.java
  #14 = NameAndType        #7:#8          // "<init>":()V
  #15 = Class              #22            // java/lang/System
  #16 = NameAndType        #23:#24        // out:Ljava/io/PrintStream;
  #17 = Utf8               helloworld
  #18 = Class              #25            // java/io/PrintStream
  #19 = NameAndType        #26:#27        // println:(Ljava/lang/String;)V
  #20 = Utf8               demo/multithread/Sync2
  #21 = Utf8               java/lang/Object
  #22 = Utf8               java/lang/System
  #23 = Utf8               out
  #24 = Utf8               Ljava/io/PrintStream;
  #25 = Utf8               java/io/PrintStream
  #26 = Utf8               println
  #27 = Utf8               (Ljava/lang/String;)V
{
  public demo.multithread.Sync2();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  public synchronized void testSync2();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String helloworld
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 6: 0
        line 7: 8
}
SourceFile: "Sync2.java"
package demo.multithread;

public class Sync3 {

    public synchronized static void testSync2() {
        System.out.println("helloworld");
    }

}
$ javap -v Sync3.class 
Classfile Sync3.class
  Last modified 2021-5-15; size 411 bytes
  MD5 checksum 546d7d535763ab592ccc73b012ccbc03
  Compiled from "Sync3.java"
public class demo.multithread.Sync3
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#14         // java/lang/Object."<init>":()V
   #2 = Fieldref           #15.#16        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #17            // helloworld
   #4 = Methodref          #18.#19        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #20            // demo/multithread/Sync3
   #6 = Class              #21            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               testSync2
  #12 = Utf8               SourceFile
  #13 = Utf8               Sync3.java
  #14 = NameAndType        #7:#8          // "<init>":()V
  #15 = Class              #22            // java/lang/System
  #16 = NameAndType        #23:#24        // out:Ljava/io/PrintStream;
  #17 = Utf8               helloworld
  #18 = Class              #25            // java/io/PrintStream
  #19 = NameAndType        #26:#27        // println:(Ljava/lang/String;)V
  #20 = Utf8               demo/multithread/Sync3
  #21 = Utf8               java/lang/Object
  #22 = Utf8               java/lang/System
  #23 = Utf8               out
  #24 = Utf8               Ljava/io/PrintStream;
  #25 = Utf8               java/io/PrintStream
  #26 = Utf8               println
  #27 = Utf8               (Ljava/lang/String;)V
{
  public demo.multithread.Sync3();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  public static synchronized void testSync2();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String helloworld
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 6: 0
        line 7: 8
}
SourceFile: "Sync3.java"
三. Synchronized与lock的区别
  1. synchronized阻塞的线程不能被中断,一直被阻塞导致效率低;而lock里可以使用trylock,让线程获取不到锁,就去干别的事情。

  2. synchronized会自动获取锁及释放锁,而lock需要手动去lock及unlock。

上一篇下一篇

猜你喜欢

热点阅读