并发编程3 锁

java锁

  1.乐观锁

  乐观锁是一种思想,认为读多写少,遇到并发修改的可能性低。每次修改数据之前都认为别人不会修改所以不用上锁,但是在修改值后会比较修改前和修改后的版本号。如果版本号一致则认为更新有效,如果版本号发生了改变则认为更新无效。java中的乐观锁基本都是通过CAS操作实现的。CAS是一种原子操作。

  2.悲观锁

  悲观锁恰恰与乐观锁相反,认为读少写多,遇到并发修改的可能性高。每次读写数据前线对数据上锁,这样别人就不能对数据进行操作指导自己完成修改后释放锁。java中的悲观锁通过Synchronized实现。AQS框架下会先尝试乐观锁更新,乐观锁失败后会转为悲观锁。

  3.自旋锁

  4.synchronized同步锁

  synchronized的作用范围:

    1.作用于非静态方法时,锁住的是对象的实例(this).

    2.作用于静态方法时,锁住的是Class实例。因为Class相关数据存储在永久代/元空间(metaspace),是全局共享的。所以此时是一个全局锁。

    3.synchronized锁住对象实例时,锁住了所有的以该对象为锁的代码块。同理sunchronized锁住的是Class实例时锁住的也是所有以Class实例为锁的代码块。

  synchronized核心组件:

    1. Wait Set: 那些调用wait方法的被阻塞的线程放在这里。

    2.Contention List:竞争队列,所有请求所的线程首先被放在这个竞争队列中。  

    3.Entry List:Contention List中有资格成为候选资源的线程被移动到Entry List中。

    4.OnDeck:任意时刻,最多只有一个线程正在竞争锁资源,该线程被称为OnDeck。

    5. Owner:当前已经获取到锁资源的线程称为Owner。

    6.!Owner:当前释放锁的线程。

  synchronized实现:

    

 

    1.JVM每次从队列的尾部取出一个数据用于竞争锁的候选者(OnDeck),但是在并发情况下,ContentionList会被大量的并发线程进行CAS访问。为了降低对尾部元素的竞争,JVM会将一部分线程移动到EntryList中作为候选竞争线程。

    2.Owner线程在unlock时,将ContentionList中的部分线程迁移到EntryList中,并指定EntryList中的某个线程为OnDeck线程(一般就是第一个进入进入集合的线程)。

    3.Owner线程并不直接把锁传递给OnDeck线程,而是吧锁竞争的权利交给OnDeck,OnDeck需要重新竞争锁。这样虽然牺牲了一些公平性,但是可以极大的提升系统的吞吐量,在JVM中,也把这种选择行为称为“竞争切换”。

    4.OnDeck线程在获取到锁后称为新的Owner,而没有获取到资源的线程任然留在EntryList中。如果Owner被wait阻塞则转移到WaitSet队列中,直到某个时刻被notify或者notifyAll唤醒会重新进入到EntryList中等待称为OnDeck。

    5.处理ContentionList、EntryList、WaitSet中的线程都处于阻塞状态,该阻塞是由操作系统来完成的。

    6.synchronized是非公平锁。synchronized在线程进入ContentionList时,等待的线程会先尝试自选获取锁,如果自选没有获取到锁则进入ContentionList。这明显对已经进入队列的线程是不公平的,还有一个不公平的事情就是自选获取锁的时候还有可能直接抢占OnDeck线程的锁资源。

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

    8.synchronized是一个重量级操作,需要调用操作系统相关接口,性能是低效的,有可能给线程加锁消耗的时间比有效的操作的时间更长。

    9.java1.6对synchronized进行了很多的优化,有适应自旋、锁消除、锁粗化、轻量级锁向偏向锁等,效率有了本质上的提高。在java1.7/1.8中都对该关键字的实现机制做了优化,引入了偏向锁和轻量级锁。都是在对象的头中有标记位置。不需要经过操作系统加锁。

    10锁可从偏向锁升级到轻量级锁再升级到重量级锁。这种升级的过程叫做锁膨胀。

    11.jdk1.6中默认开启偏向锁和轻量级锁,可以通过-XX:UseBiasedLocking来禁用偏向锁。

  ReentrantLock实现:

    

public class ReentrantLockTest
{
    private static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        lock.lock();
        System.out.println("========");
        lock.unlock();
    }
}

  Condition类和Object类锁方法的区别:

    1.Condition.await和Object对象的wait方法等效。

    2.Condition.signal方法和Object对象的notify方法等效。

    3.Condition.signalAll方法和Object对象的notifyAll方法等效。

    4.ReentrantLock类可以唤醒指定条件的线程,而Object对象的唤醒是随机的。

  tryLock和lock和lockInterruptibly的区别:

    1.tryLock能获得锁就返回true,不能返回就立即返回false,tryLock(long timeout,TimeUnit unit)可以增加时间限制,如果超过该时间段还没有获得锁,返回false。

    2.lock能获得锁就返回true,不能的话阻塞等待

    3.lock和lockInterruptibly,如果两个想成分别执行这两个方法,但此时中断这两个线程,lock不会跑出异常,而lockInterruptibly会抛出异常。

posted @ 2021-11-18 11:05  democ  阅读(89)  评论(0)    收藏  举报