关于Java 偏向锁,轻量级锁,重量级锁的应用和区别,优点和缺点

 

 

1,偏向锁。

        偏向锁:顾名思义,偏心与某一个线程锁,而他偏心的线程就是第一个访问该锁的线程,在某个锁第一次被一个线程所访问的时候,该锁会在她的Mark Word中记录该线程的线程id,而在该线程第二次去访问这个锁的时候,只需进行一次CAS操作,去检验该线程是不是他所偏向的锁(其实就是检测Mark Word中保存的线程id是否和当前线程所匹配),那么该线程访问临界资源的时候,不需要复杂的CAS操作,并且在偏向锁的时候是,是不会触发同步的。

        偏向锁的使用,在单线程(某一线程)重复访问某段临界资源,可以看做没有并发发生,无需上下文切换和CSA操作,提高了效率。但是它的适用情况当然也是单线程资源访问,如果在使用偏向锁的时候,有其他资源竞争,那么偏向锁会升级成为轻量级锁。且不可降级。

2,轻量级锁。

         轻量级锁锁允许多个线程并发访问锁,在获取锁成功的时候,执行同步代码,获取锁失败,自旋获取锁,自旋失败的情况下,锁膨胀成为重量级锁。

        轻量级锁在获取锁的过程:

        线程在执行同步代码之前,JVM会在当前线程的栈帧中创建一个空间用来存储锁记录,然后把对象头的Mark Word复制到该锁记录中,然后尝试使用CAS将对象头中的Mark Word 替换成指向锁记录的指针,

        如果成功则持有锁的线程执行同步代码,执行完CAS替换Mark Word成功释放锁,如果CAS成功,线程结束,CAS失败说明期间有线程尝试获得锁并自旋失败,轻量级锁升级成为了总量级锁。

        如果失败,线程自旋,自旋成功则获得锁,自旋失败,则膨胀成为重量级锁,线程阻塞。(对象头锁标志位改变。MarkWord改变);

        轻量级锁在竞争锁的时候不会阻塞,提高了程序的响应速度,但是在一直不能获取锁的时候会自旋消耗cpu,使用场景:追求响应速度,同步块执行速度非常快的情况下(临界资源需要执行时间比较短),程序不会因为长时间自旋等待锁,而自旋失败

3,重量级锁。

   代表Synchronized锁

 Synchronized内部实现过程

      Waiting Queue 等待队列 

            ContentionList:竞争队列,所有请求锁的线程在一开始自旋获取锁的时候,获取失败的线程会被放到这和队列中。

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

      Runnable Thread (ready thread)

            OnDeck:任意时刻,最多只有一个线程正在竞争资源,该线程被叫做OnDeck(准备齐全)

      Running Thread

            Owner:当前已经获得锁资源的线程被称为Owner.

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

 WaitSet队列是Owner线程被阻塞之后进入的队列,如果线程被唤醒notify,线程将重新进入Entry Set.

Synchronized 具体实现过程:

        JVM每次从队列的尾部去除一个线程用于竞争候选者(OnDeck),但是在并发情况下,Contention List 会被大量的线程进行CAS访问,为了降低对尾部元素的竞争,JVM会将一部分线程移动到Entry List中作为候选竞争线程(Ondeck)在Owner线程在unlock时,Owner线程并不直接把锁传递给OnDeck线程,而是把锁竞争的权利交给Ondeck线程,OnDeck线程会重新竞争锁。这样虽然牺牲了一些公平性,但是能极大提升系统的屯雨量,在jvm中也将这种行为称为竞争切换。

        OnDeck线程在获取锁资源之后会编程Owner线程,而没有得到锁资源的让然停留在Entry List,如果Owner线程被阻塞,则转移到WiteSet队列中,直到某个时刻通过notify或者notiyAll唤醒,会重新进去Entry List.

         处于ContrntionList,Entry List,WaitSet中的线程都处于阻塞状态,该状态是由操作系统实现的。

 Synchronized锁是不公平的锁:

                            1,对于刚进来过去synchronized锁的线程(在进入ContentionList队列之前),先通过自旋获取锁,如果自旋获取锁失败,则进入Contention List,这明显对于已经进入队列的线程是不公平的。

                            2,在刚进入的线程自旋的时候,可能直接抢占OnDECK线程的锁资源。

 

4,自旋锁。

         如果持有锁的线程在很短时间内释放资源,那么那些等待的线程就不用做内核态和用户态之间的切换进入阻塞挂起状态,他们只需等一等,等持有锁的线程释放锁之后可立即获取锁,这样就可以避免用户线程和内核线程的切换的消耗。

         其实等同于说自旋就是让CPU做无用功,所以需要设定一个自旋等待的最大时间。(基本认为一个线程上下文切换的时间是最佳的一个时间,是由前一次在同一个锁上的自旋时间以及锁的拥有者状态来决定的)

如果持有锁的线程执行的时间超过自旋等待的最大时间仍然没有释放锁,就会导致其他竞争锁的的线程进入阻塞状态。

        自旋锁的优点:自旋锁尽可能的减少线程的阻塞,这对于锁的竞争不激烈,且占用锁时间非常短的代码块来说性能大幅度提升,因为自旋的消耗会小于线程阻塞挂起操作的消耗。

        自旋锁的缺点:如果锁的竞争非常的激烈,或者持有锁的线程需要长时间占用锁执行同步快,这个时候就不时候适合使用自旋锁,因为在自旋锁获取锁之前cpu一直在做无用功,线程自旋的消耗大于线程阻塞挂起操作的消耗,其他需要cpu的线程又不能获取cpu,造成cpu的浪费。

5,可重入锁:Java中的可重入锁,synchronized和ReentrantLock

        Synchronized在java中是隐式的可重入锁,ReentrantLock是显式的可重入锁

       可重入锁的意思是在一个线程持有该锁的时候,当递归获取或者重复获取该锁的时候,该线程不会被阻塞,而是再次可以获得锁去访问临界资源。

        而实现可重入锁是在锁中有一个计数的count变量,在该线程再次获取锁的时候,并发锁先去判断要获取锁的线程是否是当前持有锁的线程,若不是,则阻塞线程,若是,则count++;

在释放锁的时候,判断当前线程是否是持有锁的线程,然年后进行count--;

Count = 0 则锁被完全释放;

https://www.cnblogs.com/charlesblc/p/5994162.html

你可能感兴趣的:(Java多线程并发)