八、JVM视角浅理解并发和锁

  根据《深入理解java虚拟机》这本书总结

  提到java的并发和锁,第一反应可能回想到多线程、synchronized关键字等,那么对于jvm虚拟机,这些是如何实现的呢?或者用的什么思想实现的?

  一、JAVA内存模型

    为什么要谈到内存模型?并发编程和锁要解决的问题就是同步的问题,抛开java代码,虚拟机自身是如何实现单线程甚至是多线程保证同步的。这就需要对内存模型又了解,虚拟机如何读取、修改、保存数据,在多线程的情况下,又是如何使这些操作安全,这就是了解内存模型的出发点。

    内存模型?jvm运行时内存?这里的java内存模型跟jvm内存时的区域划分个人感觉也是有一些联系的。java内存模型主要分:主内存和工作内存。其中主内存属于线程共享的区域,可以跟运行内存的堆和方法去对应,工作内存则是线程运行时的私有内存,可以跟运行时内存的栈对应起来。

    主内存?主内存是所有线程共享的,存储一些变量(不包括方法的局部变量和方法的入参数,因为这些是线程内部私有的并不会涉及到并发问题,只包括成员变量、实例变量、静态变量等),线程不可以直接对主内存中的数据进行读写的操作。

    工作内存?工作内存是线程私有的,当线程需要使用一个变量时,不能直接读写主内存的变量,而是给线程自身设置了一个专属的工作内存,当前线程会将需要用的变量read到工作线程,然后通过load操作在工作内存中放入主内存的变量副本。线程只能对这些变量副本进行操作,操作完毕后,需要store该变量到主内存中,然后通过write操作将变量的值放入变量中。

  二、主内存和工作内存的交互操作

    上面粗略的讲了主内存和工作内存对变量的操作。那么这些操作中都涉及到哪些交互动作呢?

    1、lock:作用于主内存变量,将一个变量标示为一个线程独占的状态。

    2、unlock:将lock操作后锁定状态的变量释放出来,只有unlock后变量才能被其他线程锁定。

    3、read:读取主内存变量,并传输到工作内存,供load操作载入  

    4、load:载入read读取到的数据,将主内存传入的数据,放入工作内存的变量副本中。

    5、use:在工作内存中使用该变量,将该变量传给执行引擎

    6、assign:工作内存操作变量的值

    7、store:将工作内存的变量传输给主内存

    8、write:将store传输的变量,写入主内存对应的变量中

    在这些操作中,虚拟机指定了一些规则,如3456操作不能单独出现,不允许工作线程未进行6操作的情况下执行78操作,在对对象进行2操作之前,必须已经执行78操作等等。另外也有一些特殊情况,如被volatile修饰的变量,在工作线程中修改,其余线程也能察觉到。又比如对于long和double的变量在64位系统中,可以将读写操作分为两次32位的操作进行等等。这些就不具体理解..

    上面的操作都是原子操作(特殊情况如long、double除外),当两个线程同时运行时,在没有进行lock的情况下,如果同时read变量i值为1,放入工作内存,并且在工作内存中分别+1,在最后78操作后,最终值为2,但是两个线程分别+1,结果应该为3,这就是多线程的情况下没有同步导致的问题。下面会阐述,java提供了哪些规则/思想,在单线程和多线程来保证数据的同步。

  三、单线程的代码执行

    我们写一段代码,对于当前线程来说代码是按照一行一行,更精确的讲是按照逻辑一步一步运行的,脱离这个线程,站在其他线程的的角度看,代码确不是一步一步的执行的,有可能后面的逻辑先行执行,执行顺序是没有保证的,但是java是在经过计算,保证在结果一致的情况下去执行指令,所以对于单线程,我们是完全感知不到的。

  四、多线程的同步

    1、互斥同步:多线程并发访问共享数据,保证共享数据在同一时刻只有一个线程能访问,最基本的互斥同步就是synchronize关键字,经过代码编译后,在需要同步的前后分别形成monitorenter和monitorexit两个字节码指令,这两个字节码都需要对于的现场来锁定和解锁。另外synchronize对于同一个线程是可以重入的,如果当前线程再次执行同步方法,在原先获取锁的计数上加1,当然解锁的时候-1,只有减到0的时候,才能真正的释放锁。

     juc包中lock下reentrantlock也是一种互斥同步锁,这种锁比synchronize更加轻量级,并且更加灵活,synchronize修饰,获取不到锁的时候,需要将线程挂起,这就需要切换到操作系统内核去执行,开销也是很大的,并且reentrantlock提供了很多条件,如果获取不到锁,可以进行别的动作。

    2、非堵塞同步:上面说到的互斥同步,主要的缺点就是线程进行堵塞和唤醒导致性能问题,因此在处理方式上来说,互斥同步锁可以理解成一种悲观的并发策略,因为他每次都考虑会有多个线程对资源进行竞争,必须要在获得锁的情况下才去操作。而这边的非堵塞同步锁,则可以理解成是一种乐观引发策略。就是先进行操作,如果没有其他线程对资源竞争,则操作成功,否则操作失败,当然这里也要有标志条件去标记有没有其余线程的竞争操作。这里用到了cas的操作,比较当前值是否是预想的值,如果是,设置为新值,否则失败。

    3、另外还有几个概念:自旋锁(获取不到锁的情况,不进行挂起,线程自旋,在一定时间内还获取不到锁再挂起)、自适应自旋锁(根据之前获取该锁通过自旋方式的成功率,自适应是否要进行自旋操作)。

  

        

时间: 2024-10-04 00:22:09

八、JVM视角浅理解并发和锁的相关文章

浅谈数据库并发控制 - 锁和 MVCC

在学习几年编程之后,你会发现所有的问题都没有简单.快捷的解决方案,很多问题都需要权衡和妥协,而本文介绍的就是数据库在并发性能和可串行化之间做的权衡和妥协 - 并发控制机制. 如果数据库中的所有事务都是串行执行的,那么它非常容易成为整个应用的性能瓶颈,虽然说没法水平扩展的节点在最后都会成为瓶颈,但是串行执行事务的数据库会加速这一过程:而并发(Concurrency)使一切事情的发生都有了可能,它能够解决一定的性能问题,但是它会带来更多诡异的错误. 引入了并发事务之后,如果不对事务的执行进行控制就会

web开发中的两把锁之数据库锁:(高并发--乐观锁、悲观锁)

这篇文章讲了 1.同步异步概念(消去很多疑惑),同步就是一件事一件事的做:sychronized就是保证线程一个一个的执行. 2.我们需要明白,锁机制有两个层面,一种是代码层次上的,如Java中的同步锁,典型的就是同步关键字synchronized ( 线    程级别的).另一个就是数据库层次上的,比较典型的就是悲观锁和乐观锁. 3.常见并发同步案例分析   附原文链接 http://www.cnblogs.com/xiohao/p/4385508.html 对于我们开发的网站,如果网站的访问

并发问题,锁,怎么处理死锁,脏数据处理

SQL Server死锁总结 1. 死锁原理 根据操作系统中的定义:死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态. 死锁的四个必要条件: 互斥条件(Mutual exclusion):资源不能被共享,只能由一个进程使用. 请求与保持条件(Hold and wait):已经得到资源的进程可以再次申请新的资源. 非剥夺条件(No pre-emption):已经分配的资源不能从相应的进程中被强制地剥夺. 循环等待条件(Circu

聊聊高并发(十九)理解并发编程的几种"性" -- 可见性,有序性,原子性

这篇的主题本应该放在最初的几篇,讨论的是并发编程最基础的几个核心概念,但是这几个概念又牵扯到很多的实际技术,比如Java内存模型,各种锁的实现,volatile的实现,原子变量等等,每一个都可以展开写很多,尤其是Java内存模型,网上已经能够有很几篇不错的文章,暂时不想重复造轮子,这里推荐几篇Jave内存模型的资料: 1. JSR-133 FAQ 2. JSR-133 Cookbook 3. Synchronization and Java Memory Model 4. 深入理解Java内存模

聊聊高并发(十九)理解并发编程的几种"性" -- 可见性,有序性,原子性

这篇的主题本应该放在最初的几篇.讨论的是并发编程最基础的几个核心概念.可是这几个概念又牵扯到非常多的实际技术.比方Java内存模型.各种锁的实现,volatile的实现.原子变量等等,每个都可以展开写非常多,尤其是Java内存模型,网上已经可以有非常几篇不错的文章,临时不想反复造轮子.这里推荐几篇Jave内存模型的资料: 1. JSR-133 FAQ 2. JSR-133 Cookbook 3. Synchronization and Java Memory Model 4. 深入理解Java内

眉目传情之并发无锁环形队列的实现

眉目传情之并发无锁环形队列的实现 Author:Echo Chen(陈斌) Email:[email protected] Blog:Blog.csdn.net/chen19870707 Date:October 10th, 2014 前面在<眉目传情之匠心独运的kfifo>一文中详细解析了 linux  内核并发无锁环形队列kfifo的原理和实现,kfifo鬼斧神工,博大精深,让人叹为观止,但遗憾的是kfifo为内核提供服务,并未开放出来.剑不试则利钝暗,弓不试则劲挠诬,鹰不试则巧拙惑,马不

java并发 lock锁

Java并发编程:Lock 在上一篇文章中我们讲到了如何使用关键字synchronized来实现同步访问.本文我们继续来探讨这个问题,从Java 5之后,在java.util.concurrent.locks包下提供了另外一种方式来实现同步访问,那就是Lock. 也许有朋友会问,既然都可以通过synchronized来实现同步访问了,那么为什么还需要提供Lock?这个问题将在下面进行阐述.本文先从synchronized的缺陷讲起,然后再讲述java.util.concurrent.locks包

Redis如何实现高并发分布式锁?

众所周知,分布式锁在微服务架构中是重头戏,尤其是在互联网公司,基本上企业内部都会有自己的一套分布式锁开发框架.本文主要介绍使用Redis如何构建高并发分布式锁. 假设 存在一个SpringBoot的控制器,其扣减库存的业务逻辑如下: @Autowired private StringRedisTemplate stringRedisTemplate; @RequestMapping(value = "/deduct-stock") public String deductSotck()

生产者消费者模式下的并发无锁环形缓冲区

上一篇记录了几种环形缓冲区的设计方法和环形缓冲区在生产者消费者模式下的使用(并发有锁),这一篇主要看看怎么实现并发无锁. 0.简单的说明 首先对环形缓冲区做下说明: 环形缓冲区使用改进的数组版本,缓冲区容量为2的幂 缓冲区满阻塞生产者,消费者进行消费后,缓冲区又有可用资源,由消费者唤醒生产者 缓冲区空阻塞消费者,生产者进程生产后,缓冲区又有可用资源,由生产者唤醒消费者 然后对涉及到的几个技术做下说明: ⑴CAS,Compare & Set,X86下对应的是CMPXCHG 汇编指令,原子操作,基本