Java-synchronized

2020-10-09  本文已影响0人  Android_Gleam

从代码入手,先看下代码

public class SyncTest {
    public int count;

    public  void get(){
        //同步代码块
        synchronized(this) {
            count = count + 1;
        }
    }

    public static void main(String[] args) {

    }
}

注意这里我们加锁的方式是同步代码块,然后反编译下class文件,看一下get方法

 public void get();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter                    //重点
         4: aload_0
         5: aload_0
         6: getfield      #2                  // Field count:I
         9: iconst_1
        10: iadd
        11: putfield      #2                  // Field count:I
        14: aload_1
        15: monitorexit                     //重点
        16: goto          24
        19: astore_2
        20: aload_1
        21: monitorexit
        22: aload_2
        23: athrow
        24: return

这里我们可以看到这两行,第3行monitorenter和第15行monitorexit,如果我们把锁去掉,这两行代码就不存在了,这就是加锁的秘诀,编译器会在代码中为我们插入这两个指令。

我们可以将其看成一个对象,也就是我们拿锁所拿到的对象。synchronized就是通过这两个指令实现的。

下面我们将上面的同步代码块改为:

public synchronized void get() {
        count = count + 1;
    }

然后在看下编译后的class文件:

 public synchronized void get();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED  //重点
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: aload_0
         2: getfield      #2                  // Field count:I
         5: iconst_1
         6: iadd
         7: putfield      #2                  // Field count:I
        10: return

我们发现那两个指令不见了,flags中多了ACC_SYNCHRONIZED,将方法标记成了一个同步方法。虽然在字节码中我们看不到那两条指令了,但是底层实现也是同样的原理。

原理

使用monitorenter和monitorexit指令实现的

synchronized的优化(JDK1.6开始)

重量级锁

访问同步代码块的时候,竞争失败的线程会被挂起,发生上下文切换,每次上下文切换大约耗费3-5微秒,挂起一起,唤醒一次。
缺点:线程阻塞,响应时间慢。
使用场景:适用于追求吞吐量,同步代码块执行速度较长。

轻量级锁

CPU执行一条指令的时间基本在0.6纳秒,假设我们的同步代码块中的逻辑很简单,执行速度很快,则不将线程挂起,通过CAS操作来加锁和解锁,避免上下文切换。
自适应自旋锁(概念):过度的自旋操作也会造成CPU的资源浪费,控制自旋次数,由虚拟机自行进行判定,动态进行调整,不会超过一个上下文切换所需的时间,如果超过,则将升级为重量级锁。
缺点:如果始终得不到锁,竞争的线程使用自旋会消耗CPU。
使用场景:适用于追求响应时间,同步代码块执行速度非常快。

偏向锁

大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。无竞争时不需要进行CAS操作来加锁和解锁。第一次拿锁通过CAS操作,之后在拿锁判断上次拿锁的是不是自己,如果是自己就直接用,不需要在进行CAS操作。如果有竞争则升级为轻量级锁,会导致用户线程全部被挂起,竞争激烈时不可用。
缺点:如果线程间存在竞争,会带来额外的锁撤销的消耗。
使用场景:适用于只有一个线程访问同步块的场景。

上一篇 下一篇

猜你喜欢

热点阅读