首页 > 其他 > 详细

并发编程-JMM&Lock锁以及原理

时间:2021-06-06 16:23:12      阅读:9      评论:0      收藏:0      [点我收藏+]

并发编程-JMM&Lock锁以及原理

JMM(Java Memory Model(Java内存模型)):我们都明白java是一个一次编译多处运行的语言,然而在不同的系统架构中拥有不同的内存模型,java是一个跨平台的虚拟系统,所以他有制定了自己的内存模型内存模型描述了程序中各个变量之间的关系,以及这些变量如何存取的底层细节。因为是多线程,我们就不得不考虑有序性、原子性、和可见性的问题,那jJMM就和这些问题有关,所以我们来对jmm进行认知。

Lock:他的作用和synchronized的是一样的,都是解决程安全问题的一个手段,是JUC(java.util.concurrent)下的一个工具

JMM:

实际他定义了一个在java中线程访问内存的规范,他类似于一个策略模式,在不同的系统架构中,他生成不同的指令 从而达到不同的架构运行结果的相同线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本,这样做只是把对cpu的操作方法抽象到jvm中去实现而已。

技术分享图片

jmm如何保障可见性:

前面我们说过cpu中提供了一个方法【内存屏障】,那在jvm中对应也封装了这样的指令对于不同系统架构的访问,这些实现主要是由OrderAccess类定义的一些列的读写屏障来实现的,在一系列OrderAccess类中对这下面这些指令有不同的实现方法

技术分享图片

 当我们增加一个volatile关键字后,在jvm底层的代码会进行判断,然后调用相关的指令,我们发现底层实际上是调用上面图中的storeload的指令的。

技术分享图片

 

 

 然而并不是所有的程序指令中都有指令重新排序和可见性的问题的,那就有什么时候不会有这种问题发生呢,那就引出了【happens-before

happens-before(本质上描述的是可见性规则)

happens-beore是为了约束指令重排序制定的一系列规则

  • 程序顺序性规则(as-if-series):不管指令如何重排序,单线程的程序执行结果不会发生任何变化
  • 传递性规则:hb(A, B) , hb(B, C),那么hb(A, C)。这里的hb的意思就是发生之后执行(happens-before)
  • volatile变量规则
    •   技术分享图片

       

       

  • 监视器锁规则:想象一下两个线程抢占一个锁,线程一修改了变量a,然后线程二进入读取到的变量a一定是线程一修改过后的数据
  • start规则:线程在start之间对一个变量进行修改,在线程start之后,读取的数据一定是修改后的数据
    •   技术分享图片

       

       

  • join规则:我们知道join是保障线程顺序执行的,这个的意思就是,在我join前面的线程中修改的数据,在join后一定读取到的是join前面修改的数据

Lock:

上面讲到是他的作用和synchronized是一样的只是实现的方式不一样,在他的下面有很多实现方式,我们先从【ReentrantReadWriteLock】开始,他是一种重入锁(是指任意线程在获取到锁之后,再次获取该锁而不会被该锁所阻塞),换言之他是一种互斥锁,当我们使用synchronized的时候,可以使用他进行代替。

技术分享图片

他的用法不同于synchronized,他需要手动释放锁

public class LockDemo {
    static Lock lock=new ReentrantLock();
    static  int count=0;
    private static void inc(){
        lock.lock();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
        count++;
    }


    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i <1000 ; i++) {
            new Thread(LockDemo::inc).start();
        }
        Thread.sleep(3000);
        System.out.print(""+count);
    }
}

ReentrantReadWriteLock的实现原理

本质上我们需要去满足锁的互斥,意味着同一个时间只允许一个线程进入被锁保护的代码中,以及在多线程环境下,需要满足顺序访问。

如果我们自己去设计,需要。。。

  • 锁肯定会被抢占,那我们一定要设计一个标记,去标记当前代码是否已经被别的锁抢占,类似synchronized的对象头中的锁的标记markward
  • 抢占到锁如何处理->不需要处理,直接执行代码即可
  • 没有抢占到锁如何处理
    •   需要等待(让处理排队状态的线程,直接先阻塞,这样可以释放cpu的资源)
      •   如何让线程等待
        •   wait/notify(一种线程的通讯机制,但是没有办法指定唤醒的线程
        •        LockSupport.park/unpark(阻塞一个指定的线程、唤醒一个指定的线程)
        •        condition
    •   需要排队(运行有n个线程被阻塞,此时线程处理活跃状态)
      •   通过一个数据结构把n个排队的线程存储起来
  • 锁抢占的公平性(是否允许插队,是否让队列中的线程逐一执行 )
    •   公平 (队列中的线程逐一执行)
    •   非公平(可以提升性能,这样就不用排队)
  • 抢占到锁的释放过程如何处理
    •     LockSupport.unpark(唤醒处于队列中的指定线程)

上面我们的猜想,实际上[AQS]AbstractQueuedSynchronizer已经帮我们做了

【AQS java.util.concurrent.locks.AbstractQueuedSynchronizer】AQS定义了一套多线程访问共享资源的同步器框架,他提供了两种机制,

  • 共享锁:同时可以有多个线程来获得锁
  • 互斥锁:同一时刻只能有一个线程来获得锁

整体流程为

现在有线程A、B、 C来共同抢占一个方法

  • 判断锁的状态
    •   如果无锁,
      •   那就修改aqs中state(标记锁的状态java.util.concurrent.locks.AbstractQueuedSynchronizer#state)的数值,改为1,
      •        然后把当前抢占到锁的线程放入exclusiveOwnerThread(记录当前是谁获得的锁java.util.concurrent.locks.AbstractOwnableSynchronizer#exclusiveOwnerThread)中,假设是线程A,然而修改状态不是原子的,这里底层用到了CAS操作
    •        有锁
      •   因为线程A已经获得了锁,修改了状态,然后假设现在是线程B,那这个时候线程B就需要去被放在队列中,这个队列是一个双向列表,线程C也会被放在这个双向列表中,他们被封装成一个个node,互相指向对方,
      •   现在他们每个节点都开始自旋(不断的判断state的状态,然后去抢占锁,其实是一次自旋后,发现没有可用的锁,那本身就阻塞,调用  LockSupport.park(this))当线程A释放了锁之后,
      •   他们就会被唤醒(LockSupport.unpark(头节点的下一个节点)),记住,这里只唤醒头节点的下一个节点,那就是线程B了,这个时候线程B会继续自旋,然后去抢占锁,这里就体现了公平性(是否允许插队),如果这个在线程A释放锁的时候,这个时候来个线程D那么刚好这个时候是没有锁的,那线程D就有可能抢到线程B的前面

技术分享图片

 

并发编程-JMM&Lock锁以及原理

原文:https://www.cnblogs.com/UpGx/p/14855323.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!