synchronized

经典面试题之:synchronized

一般说起Synchronized第一反应就是java锁,这次我们不从底层代码开始谈,而是先从Synchronized的用法上开始聊起来。
Synchronized可以作用在3个地方,分别是锁对象,锁方法,锁代码块

先看下Synchronized的一些用法

在方法上家Synchronized关键字
public class SysThread implements Runnable {

    private Integer i = 0;

    public  void addMyself() {
        int temp = i;
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        temp++;
        i = temp;
        System.out.println(Thread.currentThread().getName() + ".." + i);

    }

    @Override
    public void run() {
        for (int j = 0; j < 5; j++)
            addMyself();
    }
}

public class SysThreadTest {

    public static void main(String[] args) {

        SysThread sysMainTest = new SysThread();

        Thread thread1 = new Thread(sysMainTest,"thread1");
        Thread thread2 = new Thread(sysMainTest,"thread2");
        thread1.start();
        thread2.start();

    }

}

这个是一个没有加Synchronized的代码,执行结果如下

thread2..1
thread1..1
thread1..2
thread2..2
thread1..3
thread2..3
....

这里我们可以发现在SysThread里面i被两个线程执行的时候,并没有按照我们的想法递增,因为在addMyself的时候发生了同步的问题

我们在addMyself的方法上家一个Synchronized,在看下

    public synchronized void addMyself() {
        ...
    }
    执行结果如下
    hread2..1
thread2..2
thread2..3
thread2..4
thread2..5
thread1..6
thread1..7
thread1..8
...

可以看到结果已经按照我们预先摄像的那样i做了自增操作。这是因为我们在方法上家了Synchronized,每次只允许一个线程进入该方法执行

在方法内的代码块增加Synchronized关键字,锁住i
synchronized (i) {
            int temp = i;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            temp++;
            i = temp;
            System.out.println(Thread.currentThread().getName() + ".." + i);
        }
        
执行结果如下
thread1..1
thread1..2
thread1..3
thread1..4
thread1..5
thread2..6
...

我们可以就看到执行结果和在方法上增加是一样的下效果

锁住对象,这里代码块里锁贼classs和在静态方法上增加Synchronized都属于锁对象的情况
public void addMyself() {
        synchronized (SysThread.class) {
           ...
        }
    }
    执行结果如下
    thread1..1
thread1..2
thread1..3
thread2..4
thread2..5
thread2..6
...

通过上面的几个例子,我们可以看到Synchronized可以起到锁的作用。

    public static void main(String[] args) {
          //锁代码块
        synchronized (SyncDemo1.class){
            System.out.println("this");
        }
    }

    //锁方法
    public synchronized void method1(){
        System.out.println("method1");
    }

    //锁静态方法
    public synchronized static void method2(){
        System.out.println("method2");
    }

以上代码执行javap -v xxx.class之后结果如下

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: getstatic     #2                  // Field lock:Ljava/lang/String;
         3: dup
         4: astore_1
         5: monitorenter
         6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         9: ldc           #4                  // String this
        11: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        14: aload_1
        15: monitorexit
        16: goto          24
        19: astore_2
        20: aload_1
        21: monitorexit
        22: aload_2
        23: athrow
        24: return
      Exception table:
         from    to  target type
             6    16    19   any
            19    22    19   any
      LineNumberTable:
        line 13: 0
        line 14: 6
        line 15: 14
        line 16: 24
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      25     0  args   [Ljava/lang/String;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 19
          locals = [ class "[Ljava/lang/String;", class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4

  public synchronized void method1();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #6                  // String method1
         5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 19: 0
        line 20: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Ldemo7/SyncDemo1;

  public static synchronized void method2();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #7                  // String method2
         5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 23: 0
        line 24: 8
  1. 可以看到main方法中锁住代码块主要由以下指令实现 monitorenter, monitorexit,
    这里有两个monitorexit,主要是出现异常也可以退出monitor。
  2. 锁方法和静态方法我们可以看到flags,有ACC_SYNCHRONIZED标识。当执行到方法的时候,检测到有这个标志位,会隐式的调用monitorenter,monitorexit指令

synchronized的特性

  1. 可重入性,synchronized是偏向锁。
  2. 不可中断性,synchronized的实现是有字节码命令monitorenter, monitorexit实现的
  3. 有序性.synchronized是排他锁,在一定时间在只能有有一个线程执行synchronized修饰的方法或者代码块,根据as-if-serial语义,虽然synchronized无法禁止指令重排序,但是只有一个线程执行的话相当于单线程执行,执行结果必然是有序的。

Synchronized锁升级

围绕着synchronized还有一个问题就是优化,下图展示了一个synchronized的锁升级过程
右边图片是一个标准的锁过程,左边是引入的偏向锁的概念
java synchronized锁插图
引入偏向锁的概念是因为在实际的引用中很可能只有一个线程在执行代码。这种情况下可以减少使用轻量级锁带来的系统开销,因为轻量级依赖CAS操作。而偏向锁只需要在替换线程Id的时候使用CAS就可以了。
详细参考 wiki https://wiki.openjdk.java.net/display/HotSpot/Synchronization

  1. 一个线程进入后,检查对象头markword偏向标识是否偏向,如果是1,确认偏向锁执行偏向锁流程,进行加锁操作,存入线程id。
  2. 当发生多个线程争抢资源时,如果对象未锁定,会撤销偏向锁,将状态改成无锁状态,如果状态已锁定,将状态设置为轻量级锁状态
  3. 轻量级锁线程通过自旋等待资源。
  4. 如果自旋需要消耗cpu资源,自旋失败,无法获得对象会进行锁膨胀。升级为重量级锁
  5. 在发生STW的时候,会触发降级操作。判断如果当前对象没有被占用,则降级为轻量级锁

锁的标志位以及锁的状态,可以参考对象头的markword。
java synchronized锁插图1

1 对 “java synchronized锁”的想法;

发表评论

邮箱地址不会被公开。