# 07.SYN关键字

153 阅读5分钟

07.SYN关键字辨析

1.Synchronized的基本用法和原理

 synchronized 是jvm 里面实现的一种锁机制,本质是一种关联对象的对象锁. synchronized关键字最主要有以下3种应用方式

  • 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
  • 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
  • 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

synchronized 本质是一种对象锁,一般描述上,对于修饰静态方法,习惯上称之为类锁,但其实是获取类的getClass() 对象,对于Class 对象进行的加锁.要想synchronized同步锁的机制起作用,关键是保证被锁的对象的一致性.

为什么一定称之为对象锁,是因为和其实现机制有着原因的. 在jvm 对象的堆内存中,不仅仅是有我们自己实现的类里面的一些成员变量,还有很多块区域,比如说标记了对象分代年龄,对象的hashcode,以及对象的锁标志位和对象锁关联的Monitor监视器对象地址.

在执行到synchronized 修饰的方法或者代码块时候,jvm 会执行一个命令monitor.enterlock(),Monitor监视器会执行count+1 操作,执行完毕同步代码块后会执行一个monitor.exitlock()操作,Monitor监视器会执行count-1.当Monitor监视器的count 计数值为0时候,属于没有线程持有锁状态.

以上是synchronized 实现的重量级锁的机制,下面是JVM对于synchronized 优化的几种锁,了解概念就好,属于JVM底层优化,与应用开发无关.

2.JVM

image.png 因为重量级锁,牵扯到线程的上下文切换,所以后来java 1.6 以后对于Synchronized 锁机制进行了优化. 的状态总共有四种,无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁,但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级.

  • 偏向锁 它是一种针对加锁操作的优化手段,JVM 会分析当前的运行环境,要是发现当前锁总是由同一线程获得,无其他线程竞争状态,为了减少同一线程获取锁的代价(一般一些CAS的操作)而引入偏向锁. 偏向锁的核心思想是,如果一个线程获得了锁,那么锁就进入偏向模式,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作,从而也就提供程序的性能。所以,对于没有锁竞争的场合,偏向锁有很好的优化效果,毕竟极有可能连续多次是同一个线程申请相同的锁。但是对于锁竞争比较激烈的场合,偏向锁就失效了,因为这样场合极有可能每次申请锁的线程都是不相同的,因此这种场合下不应该使用偏向锁,否则会得不偿失,需要注意的是,偏向锁失败后,并不会立即膨胀为重量级锁,而是先升级为轻量级锁。
  • 自适应自旋锁 当有其他线程竞争锁资源时候,偏向锁会升级为自旋锁.采用CAS机制进行有限时间的自旋锁,一般JVM 控制自旋上限时间不固定,一般是上一个线程获取锁的时间或者是线程上下文切换的时间,具体由JVM 分析决定.因为长时间自旋对于CPU会造成压力. 自适应自旋解决的是“锁竞争时间不确定”和"线程上下文切换"的问题.
  • 轻量级锁 旋锁的目标是降低线程切换的成本。如果锁竞争激烈,我们不得不依赖于重量级锁,让竞争失败的线程阻塞;如果完全没有实际的锁竞争,那么申请重量级锁都是浪费的。轻量级锁的目标是,减少无实际竞争情况下,使用重量级锁产生的性能消耗,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。

顾名思义,轻量级锁是相对于重量级锁而言的。使用轻量级锁时,不需要申请互斥量,仅仅将Mark Word中的部分字节CAS更新指向线程栈中的Lock Record,如果更新成功,则轻量级锁获取成功,记录锁状态为轻量级锁;否则,说明已经有线程获得了轻量级锁,目前发生了锁竞争(不适合继续使用轻量级锁),接下来膨胀为重量级锁。

3.几种锁

image.png

image.png

这几种锁更像是一种分析维度,而不是某种锁叫这个名字. 比如说 synchronized锁,属于非公平锁,因为每个进入的线程都会首先获取一遍锁,失败后才会进入等待队列.像 ReentrantLock 为了效率优先,默认属于非公平锁,可以在初始化时候指定公平锁.

4. 读写锁

如果很多线程从一个数据结构中读取数据,但是很少线程写入数据,读写锁是很有用的. 毕竟读 和读操作共享访问可以极大提高效率. 使用读写锁的步骤: 1.构造一个读写锁的对象.

private final ReadWriteLock rw = new ReentrantReadWriteLock();

2.分别抽取读锁和写锁

private final Lock rl = rw.readLock();
private final Lock wl = rw.writeLock();

3.对获取方法加上读锁

public double read(){
    rl.lock();
    try{....}
    finally{
    rl.unLock();
    }
}

4.对于写入操作加上写锁

public void write(){
    wl.lock();
    try{...}
    finally{
     wl.unLock();
    }
}
  • Lock readLock(); 可以得到一个被多个读操作共享的读锁,但是会排斥所有的写操作.
  • Lock writeLock(); 可以得到一个写锁,但是排斥所有其他的读操作和写操作