多线程第七旅

4.1.4  Lock锁-JDK1.5(JUC)-java语言层锁

代码如下:

package www.wl.java;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class MyThread implements Runnable { // 线程主体
    private int ticket = 100;
    private Lock lock = new ReentrantLock ();

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                lock.lock ();
                if (ticket > 0) {
                    try {
                        Thread.sleep (200);
                    } catch (InterruptedException e) {
                        e.printStackTrace ();
                    }
                    System.out.println (Thread.currentThread ().getName () + "还剩下" + this.ticket-- + "");
                }
            }catch(Exception e){
                e.printStackTrace ();
            }finally{
                lock.unlock ();
            }
        }
    }
}

public class 多线程 {
    public static void main(String[] args) {
        MyThread mt = new MyThread() ;
        new Thread(mt,"黄牛A").start();
        new Thread(mt,"黄牛B").start();
        new Thread(mt,"黄牛C").start();
    }
}

 

4.1.5   CAS(Compare and Swap)--乐观锁

 

--基于synchronized的优化(等待时间的优化)

 

悲观锁:线程获取锁(JDK1.6之前建锁,内建锁(synchronized))是一种悲观锁的策略

 

假设每一次执行临界区代码(访问共享资源)都会产生冲突,所以当线程获取到锁的同时也会阻塞其他尝试获取该锁的线程

 

乐观锁:(CAS操作,无锁操作(lock体系)):假设所有线程访问共享资源不会出现冲突,由于不会出现冲突自然就不会阻塞其他线程,因此就不会出现阻塞停顿的状态

出现冲突时,无锁操作使用CAS来鉴别线程是否出现冲突,出现冲突就重试当前操作直到没有冲突为止

 

4.1.5.1  CAS操作过程

CAS可以理解为CAS(V,O,N):

V:当前内存地址实际存放的值

O:预期值(旧值)

N:更新的新值

 

当执行CAS后,如果V==0时,期望值与内存实际值相等,该值没有被任何其他线程修改过,因此可以将N替换到内存中

 

当V=!0.表明该值已经被其他线程修改过,所以无法将N替换,返回最新的值V

 

当多个线程使用CAS操作同一个变量时,只有一个线程会成功,并成功更新变量值,其余线程均会失败,失败线程会重新尝试或将线程挂起(阻塞)

 

原老级内建锁:

最主要的问题:当存在线程竞争的情况下会出现线程阻塞以及唤醒带来的性能问题,对应互斥同步(阻塞同步),效率很低

而CAS并不是武断的将线程挂起,会尝试若干次CAS操作,并非进行耗时的挂起与唤醒操作,因此非阻塞式同步

 

4.1.5.2 CAS问题

1.ABA问题:

解决思路:沿用数据库的乐观锁机制,添加版本号1A-2B-3A

JDK1.5提供atomic包下AtomicStamoedReference类来解决CAS的ABA问题

 

 

2.自旋(CAS)会浪费大量的处理器资源(CPU)---踩刹车

与线程阻塞相比,自旋会浪费大量的处理器资源,因为此时线程仍处于运行状态,只不过跑的是无用指令,期望在无用指令时,锁能被释放出来

 

 

解决思路:自适应自旋(重量级锁的优先)

根据以往自旋等待时能否获取到锁,来动态调整自旋的时间(循环尝试的数量)

 

如果在上一次自旋时获取到锁,则此次自旋时间稍微变长一点,如果在上一次自旋结束还没有获取到锁,此次自旋时间稍微短一点

 

3.公平性

处于阻塞状态的线程无法立即竞争被释放的锁,而处于自旋状态的线程很有可能先获取到锁

 

内建锁无法实现公平性

LOCK体系可以实现公平锁

 

4.1.5.3  对象头

 

1.JDK1.6之后对内建锁做了优化(新增偏向锁,轻量级锁)

 

 

无锁状态0 01

偏向锁1 01

轻量级锁00

重量级锁(JDK1.6之前)-(内建锁)   10

 

这四种状态随着竞争情况逐渐升级,锁可以升级不能降级,为了提高获得锁与释放锁的效率

 

  1. 偏向锁

偏向锁:最乐观的锁,从始至终只有一个线程请求一把锁

 

偏向锁的获取:

当一个线程访问同步代码块并获取锁时,会在对象头和栈帧中的锁记录里记录存储偏向锁的线程ID,以后该线程在进入和退 出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word中偏向锁线程ID是否是当前线程ID。

如果测试成功,表示线程已经获得了锁直接进入代码块运行

 

如果测试失败,检查当前偏向锁字段是否为0.如果为0,采用CAS操作将偏向锁字段设置为1,并且更新自己的线程ID到Mark Word字段中

 

如果为1,表示此时偏向锁已经被别的线程获取,则此线程不断尝试使用CAS获取偏性锁或者将偏向锁撤销,升级为轻量级锁(升级概率较大)

 

偏向锁的撤销:

偏向锁使用一种等待竞争出现才释放锁的机制,当有其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放偏向锁。

 

小tip:偏向锁的撤销开销较大,需要等到线程进入全局安全点safepoint(当前线程在CPU上没有任何有用字节码)

 

偏向锁头部Epoch字段值:表示此对象偏向锁的撤销次数,默认撤销40次以上,表示此对象不再适用于偏向锁,当下次线程再次获取此对象时,直接变为轻量级锁

 

如何关闭偏向锁:

偏向锁在JDK6之后是默认启用的,但是它在应用程序启动几秒钟之后才激活,-XX:BiasedLockingStartupDelay=0。将延迟关闭,JVM一启动就激活偏向锁

 

关闭偏向锁:-XX:-UseBiasedLocking=false,程序默认会进入轻量级锁状态

只有一次CAS过程,出现在第一次加锁时

 

 

2.轻量级锁

轻量级锁:多个线程在不同时间段请求同一把锁,也就是基本不存在锁竞争,针对此种情况,JVM采取轻量级锁来避免线程的阻塞式以及唤醒

 

加锁:

线程在执行同步代码块之前,JVM先在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头的mark word 字段直接复制到此空间中

然后线程尝试使用CAS将对象头的mark word替换为指向锁记录的指针(指向当前线程)

如果成功表示获取到轻量级锁,如果失败,表示其他线程竞争轻量级锁,当前线程便使用自旋来不断尝试

 

解锁

轻量级解锁时,会使用CAS将复制的 Mark Word替换回到对象头,如果成功,则表示没有竞争发生,正常解锁。 如果失败,表示当前锁存在竞争,锁就会进一步膨胀成重量级锁

 

总结:

Java虚拟机中synchronized关键字的实现,按照代价由高到低可以分为重量级锁、轻量锁和偏向锁三种。

1. 重量级锁会阻塞、唤醒请求加锁的线程。它针对的是多个线程同时竞争同一把锁的情况。JVM采用了自适 应自旋,来避免线程在面对非常小的synchronized代码块时,仍会被阻塞、唤醒的情况。

2. 轻量级锁采用CAS操作,将锁对象的标记字段替换为一个指针,指向当前线程栈上的一块空间,存储着锁对 象原本的标记字段。它针对的是多个线程在不同时间段申请同一把锁的情况。

3. 偏向锁只会在第一次请求时采用CAS操作,在锁对象的标记字段中记录下当前线程的地址。在之后的运行过 程中,持有该偏向锁的线程的加锁操作将直接返回。它针对的是锁仅会被同一线程持有的情况。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值