美文网首页
Synchronized 的用法及底层原理

Synchronized 的用法及底层原理

作者: overflowedstack | 来源:发表于2021-05-16 12:12 被阅读0次
    一. 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文件.
    • 2.1 synchronized代码块
      class文件中,synchronized被翻译成monitorenter, monitorexit. 当线程执行到monitorenter, 就会去看String对象name的锁,是否被其它线程占有。如果没有,那么当前线程就会持有name的锁,其他线程就不能再获取name的锁,进而不能同时执行同步代码块了。
      当线程执行到monitorexit,就会释放持有的name的锁,那么其他线程就可以去竞争name的锁。
    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"
    
    • 2.2 synchronized方法
      synchronized修饰方法时,在class文件中,方法上有flags: ACC_PUBLIC, ACC_SYNCHRONIZED。当方法被调用时,会检查标志位ACC_SYNCHRONIZED是否被设置,如果被设置了,就会去执行monitorenter, monitorexit这一套操作。
    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"
    
    • 2.3 synchrnozed静态方法
      synchronized修饰静态方法时,方法上有flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED。当方法被调用时,会检查标识位ACC_STATIC, ACC_SYNCHRONIZED是否被设置。如果被设置,就会对class对象进行monitorenter, monitorexit操作。
    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。

    相关文章

      网友评论

          本文标题:Synchronized 的用法及底层原理

          本文链接:https://www.haomeiwen.com/subject/clzqjltx.html