美文网首页
带你深入理解和解剖 synchronized

带你深入理解和解剖 synchronized

作者: charlven | 来源:发表于2020-04-05 17:26 被阅读0次

    1. 是什么?

    synchronized 是 JAVA 关键字,用在方法或代码块。控制方法或代码同一时间只有一个线程执行,用来解决多线程同时访问出现的并发问题。

    2. 使用分类(作用域)

    对象锁(代码块)

    • 表现形式1:加在方法上
    private synchronized boolean sellTicketsWithSyncMethod(int count) {
        if (totalTickets - count < 0) {
            return false;
        } else {
            totalTickets = totalTickets - count;
            return true;
        }
    }
    
    • 表现形式2:加在对象上

    锁多个对象时,相互不干扰

    private final Object LOCK = new Object();
    private boolean sellTicketsWithSyncObject(int count) {
        synchronized (LOCK) {
            if (totalTickets - count < 0) {
                return false;
            } else {
                totalTickets = totalTickets - count;
                return true;
            }
        }
    }
    
    • 表现形式3:this当前对象上
    private boolean sellTicketsWithSyncObject(int count) {
        synchronized (this) {
            if (totalTickets - count < 0) {
                return false;
            } else {
                totalTickets = totalTickets - count;
                return true;
            }
        }
    }
    

    类锁

    • 表现形式1:static
    public static synchronized void staticMethod() {
       // todo
    }
    
    • 表现形式2:synchronized(xxx.class)
    public Class SyncClass {
        private void method () {
            synchronized (SyncClass.class) {
                //todo
            }
        }
    }
    

    对象锁和类锁区别?

    对象锁锁的是一个对象。不同对象下互不干扰。而类只有一个。

    3. synchronized 的特征

    1. 它是一个可重入锁。
    2. JAVA 语言的关键字,由虚拟机字节码指令实现。(下面深入讲解)
      1. 用 monitor 来控制进出
      2. moniterenter 进入
      3. Monitorexit 为退出
    3. 不可中断,无法控制锁的超时时间,可能会造成阻塞。而 ReentranLock 可以,这个后面讲解。
    4. synchronized 只有非公平锁。 公平锁和非公平锁的区别
    5. 粒度是对象,锁的是对象而非变量。

    3.1 可重入讲解

    指的是同一线程的外层函数获得锁之后,内层函数可以直接再次获取该锁。从而避免死锁。

    演示:

    public class SynchronizedReentrantTest {
        int i = 0;
        public synchronized void method1() {
            method2();
        }
     
        private synchronized void method2() {
        }
     
        private synchronized void reentrantSelf() {
            if (i == 0) {
                i++;
                reentrantSelf();
            }
        }
     
        public static void main(String[] args) {
            SynchronizedReentrantTest instance = new SynchronizedReentrantTest();
            
            // 重入其他方法
            instance.method1();
            
            // 自己重入自己
            instance.reentrantSelf();
            
            // 当然也可以重入其他类方法,这里不演示了。
        }
    }
    

    4. 深入理解 synchronized (反编译来看看)

    如何反编译?

    1. 编译 javac xxxx.java
    2. 反编译 javap -c -p -v xxxx.class

    以下是截取的部分源码:

    public class SynchronizedMethodDif {
        //...
    
        // 作用于对象上
        private static final Object LOCK = new Object();
        private static boolean sellTicketsWithSyncObject(int count) {
            synchronized (LOCK) {
                if (totalTickets - count < 0) {
                    return false;
                } else {
                    totalTickets = totalTickets - count;
                    return true;
                }
            }
        }
        
        // 作用于 static 方法上
        private static synchronized void soutStatic() {
            System.out.println(Thread.currentThread().getName() + " is sleeping ");
            try {
                Thread.sleep(3000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " : 1");
            System.out.println(Thread.currentThread().getName() + " finish sleeping ");
        }
        
        // 作用于普通方法上
        private synchronized void soutNonStatic() {
            System.out.println(Thread.currentThread().getName() + " is sleeping ");
            try {
                Thread.sleep(3000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " : 1");
            System.out.println(Thread.currentThread().getName() + " finish sleeping ");
        }
        
        //...
    }
    

    反编译后:

    // 作用于对象上
    private static boolean sellTicketsWithSyncObject(int);
    descriptor: (I)Z
    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
        0: getstatic     #10                 // Field LOCK:Ljava/lang/Object;
        3: dup
        4: astore_1
        5: monitorenter
        ...
        16: monitorexit
        17: ireturn
        ...
        27: aload_1
        28: monitorexit
        29: ireturn
        ...
        31: aload_1
        32: monitorexit
        33: aload_2
        34: athrow
        ...
        frame_type = 75 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]
    
    ...
    // 作用于 static 上
    private static synchronized void soutStatic();
        descriptor: ()V
        flags: (0x002a) ACC_PRIVATE, ACC_STATIC, ACC_SYNCHRONIZED
        Code:
          stack=2, locals=1, args_size=0
             0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        ...
        frame_type = 4 /* same */
    
    ...
    // 作用于 普通方法 上
    private synchronized void soutNonStatic();
        descriptor: ()V
        flags: (0x0022) ACC_PRIVATE, ACC_SYNCHRONIZED
        Code:
          stack=2, locals=2, args_size=1
             0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        ...
        frame_type = 4 /* same */
    
    

    4.1 原理分析

    -> 作用于 代码块(对象) 上:

    通过 monitor 来控制,使用monitorenter 和 monitorexit 指令控制线程进出。

    monitor 讲解:

    是一个监视器,执行是依赖于对象的锁计数

    1. monitorenter:每个对象都是一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
      1. 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者
      2. 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1
      3. 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权
    2. monitorexit:执行monitorexit的线程必须是objectref所对应的monitor的所有者。指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。

    -> 作用于 方法

    • 方法使用 ACC_SYNCHRONIZED 标识
    • static 方法,则锁是作用在类上
    • 非 static 方法,则锁作用在类对象上

    相关文章

      网友评论

          本文标题:带你深入理解和解剖 synchronized

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