一文彻底解决锁的问题


欢迎访问我的个人博客(点击进入)

1.synchronized

synchronized关键字用于Java对象、方法、代码块提供线程安全的操作。

sychronized属于独占式的悲观锁,同时属于可重入锁。在使用sychronized修饰对象时,同一时刻只能有一个线程对该对象进行访问;在修饰代码块、方法时,同一时刻只能有一个线程执行该方法体或代码块。

Java所有对象中有一个monitor对象,加锁就是在竞争monitor对象。对代码块加锁是通过在前后分别加上monitorenter和monitorexit指令实现的,对方法是否加锁是通过一个标记位来判断的。

1.1.synchronized作用范围

  • synchronized作用于成员变量和非静态方法时,所著的对象是对象的实例,即this对象
  • synchronized作用于静态方法时,锁住的是Class实例,因为静态方法属于Class而不属于对象
  • synchronized作用于一个代码块时,锁住的是所有代码块中配置的对象

1.2.sychronized的实现原理

在synchronized内部包括ContentionList、EntryList、WaitSet、OnDeck、Owner、!Owner这六个区域,每个区域的数据都代表锁的不同状态。

  • ContentionList:锁竞争队列,所以请求锁的线程都被放在竞争队列中
  • EntryList:竞争候选列表,在ContentionList中有资格成为候选者的来竞争锁资源的线程被移动到EntryList中
  • WaitSet:等待集合,调用wait方法后被阻塞的线程将被放在WaitSet中
  • Ondeck:竞争候选这同一时刻最多只能有一个线程竞争索资源,该线程的状态被称为Ondeck
  • Owner:竞争到锁资源的线程被称为Owenr状态线程
  • !Owner:在Onwer线程释放锁后,会从Owner状态变为!Owner

synchronized在收到新的锁请求时首先自旋,如果通过自旋没有获得锁资源,则将被放入锁竞争队列ContentionList中。

为了防止锁竞争时ContentionList尾部的元素被大量的并发线程进行CAS访问而影响性能,Owner线程会在释放锁资源时将Contention中的部分线程移动到EntryList中,并指定EntryList中的某个线程为Ondeck线程。Owner线程并没有直接把锁传递给Ondeck线程,而是把锁竞争的权利交给Ondeck线程,让Ondeck线程重新竞争锁。该行为牺牲了公平性,但提高了性能。

获得锁资源的Ondeck线程会变为Owner线程,而未获取到锁资源的线程仍然停留在EntryList中。

Owner线程在被wait方法阻塞后,将进入WaitSet队列中,直到被notify或notifyAll方法唤醒,会在次进入到EntryList中。

ContentionList和EntryList中的线程均为阻塞状态。

示意图:

一文彻底解决锁的问题_第1张图片

在sychronized中,线程在进入ContentionList之前,等待的线程回先尝试自旋来获得锁,如果获取不到锁将会进入ContentionList,该做法对于已经进入队列等待的线程是不公平的,因此sychronized是非公平锁。另外,自旋获得锁的线程也可以直接抢占Ondeck线程的锁资源。

sychronized是一个重量级操作,JDK1.6对sychronized做了许多优化,引入了适应自旋、锁消除、锁粗化、轻量级锁及偏向锁等以提高锁的效率。锁可以从偏向锁升级到轻量级锁,在升级到重量级锁。这种升级过程叫做锁膨胀。

2.ReentrantLock

ReentrantLock在我之前的博客有写到

链接:点击进入

3.sychronized和ReetrantLock的比较

共同点

  • 都用于控制多线程对共享对象的访问
  • 都是可重入锁
  • 都保证了可见性和互斥性

不同点

  • ReetrantLock显式获取和释放锁;sychronized隐式获取锁和释放锁。为了避免程序出现异常而无法释放锁,在使用ReentrantLock时需要在finally中释放锁
  • ReentrantLock可相应中断、可轮回,为处理锁提供了更多的灵活性
  • ReentrantLock是API级别的,synchronized是JVM级别的
  • ReentrantLock可以定义公平锁
  • ReentrantLock通过Condition可以绑定多个条件
  • 底层实现不同:sychronized是同步阻塞,采用的时悲观并发策略;ReentrantLock是同步非阻塞,采用的是乐观并发策略
  • Lock是一个接口,sychronized是Java关键字,synchronized是由内置的语言实现的
  • 我们可以通过Lock知道有没有成功获取锁,通过synchronized却无法做到
  • Lock可以通过分别定义读写锁提高多个线程读操作的效率

你可能感兴趣的:(JUC)