Java多线程(4):synchronized关键字

synchronized使用

1)同步方法 返回值前加上该关键字
2)同步代码块 synchronized(monitor lock)

synchronized理解

同步代码块:
monitorenter 和 monitorexit两条字节码指令
1)每一个对象都与一个monitor相关联,monitor lock某一时刻只能被一个线程同一时间获得如果别的线程获得monitor所有权时会发生如下情况:

  • a. monitor的计数器=0,说明monitor lock并没有被获得,如果某一线程获得,monitor的计数器+1

  • b.如果线程已经拥有该monitor,只是重新进入,则进入monitor的计数器+1

  • c.如果monitor已经被占有,其他线程尝试获取时会进入Blocked状态,直到monitor计数器变为0,则再次获取

2)monitorexit字节码指令表示当前线程释放对monitor的所有,monitor的计数器-1
同步方法
常量池中的ACC_SYNCHRONIZED标识符来实现方法同步
过程如下:
方法调用,调用指令会检查该方法的ACC_SYNCHRONIZED是否被设置,如果设置,获取monitor方可执行,执行完后释放。在执行期间,其他线程都无法获取该monitor

synchronized的演变(锁升级)

  1. 偏向锁:核心思想就是如果一个线程获得了锁,那么锁就会进入偏向模式,当这个线程再次请求该锁时,无需任何的同步操作操作,因此提高程序的性能。对于没有锁竞争的场合,偏向锁有很好的优化效果,毕竟极有可能连续多次同一个线程申请相同的锁,但是对于锁竞争比较激烈的场合,偏向锁就会失效。
  2. 轻量级锁:对于绝大部分的锁,在整个同步周期内不存在竞争的,这只是经验数据
  3. 自旋锁:介于轻量级锁和重量级锁之间的虚拟机的优化手段,给定一个循环次数,在经历若干次循环,如果得到锁,就顺序执行同步代码,反之就会将线程在操作系统层面挂起,这也是为了提升效率。
  4. 重量级锁:未获取锁的线程会阻塞

synchronized锁的使用过程

JDK1.6以后版本在处理同步锁才存在锁升级的概念,JVM对于同步锁的处理是从偏向锁开始的,随着竞争越来越激烈,处理方式从偏向锁升级到轻量级锁,最终升级到重量级锁。
JVM在使用锁的过程会用到Java对象头中的Mark Word,Mark Word记录了对象和锁有关的信息,当这个对象被synchronized关键字当成同步锁时,围绕这个锁的一系列操作都和Mark Word有关。
Java多线程(4):synchronized关键字_第1张图片
synchronized锁的使用过程如下:

  1. 当没有被当成锁时,这就是一个普通的对象,Mark Word记录对象的HashCode,锁标志位是01,是否偏向锁那一位是0。
  2. 当对象被当做同步锁并有一个线程A抢到了锁时,锁标志位还是01,但是否偏向锁那一位改成1,前23bit记录抢到锁的线程id,表示进入偏向锁状态。
  3. 当线程A再次试图来获得锁时,JVM发现同步锁对象的标志位是01,是否偏向锁是1,也就是偏向状态,Mark Word中记录的线程id就是线程A自己的id,表示线程A已经获得了这个偏向锁,可以执行同步锁的代码。
  4. 当线程B试图获得这个锁时,JVM发现同步锁处于偏向状态,但是Mark Word中的线程id记录的不是B,那么线程B会先用CAS操作试图获得锁,这里的获得锁操作是有可能成功的,因为线程A一般不会自动释放偏向锁。如果抢锁成功,就把Mark Word里的线程id改为线程B的id,代表线程B获得了这个偏向锁,可以执行同步锁代码。如果抢锁失败,则继续执行步骤5。
  5. 偏向锁状态抢锁失败,代表当前锁有一定的竞争,偏向锁将升级为轻量级锁。JVM会在当前线程的线程栈中开辟一块单独的空间,里面保存指向对象锁Mark Word的引用,同时在对象锁Mark Word中保存指向这片空间的引用。上述两个保存操作都是CAS操作,如果保存成功,代表线程抢到了同步锁,就把Mark Word中的锁标志位改成00,可以执行同步锁代码。如果保存失败,表示抢锁失败,竞争太激烈,继续执行步骤6。
  6. 轻量级锁抢锁失败,JVM会使用自旋锁,自旋锁不是一个锁状态,只是代表不断的重试,尝试抢锁。从JDK1.7开始,自旋锁默认启用,自旋次数由JVM决定。如果抢锁成功则执行同步锁代码,如果失败则继续执行步骤7。
  7. 自旋锁重试之后如果抢锁依然失败,同步锁会升级至重量级锁,锁标志位改为10。在这个状态下,未抢到锁的线程都会被阻塞。那在这里还需要考虑一个问题:为什么说重量级锁开销大呢?主要是,当系统检查到锁是重量级锁之后,会把等待想要获得锁的线程进行阻塞,被阻塞的线程不会消耗cup。*但是阻塞或者唤醒一个线程时,都需要操作系统来帮忙,这就需要从用户态转换到内核态,而转换状态是需要消耗很多时间的,有可能比用户执行代码的时间还要长。*这就是说为什么重量级线程开销很大的。

CAS:Compare And Swap,是一个cpu层面的原子性操作指令。该指令存在三个参数,第一参数目标地址,第二参数值1,第三参数值2,指令会比较目标地址的内容和值1是否一致,如果一致,则将值2填写到目标地址。

synchronized保证并发编程三大特性的哪些特性

原子性,可见性,有序性都可以得到保证。

你可能感兴趣的:(Java多线程(4):synchronized关键字)