锁机制概述

Java中共享变量的内存可见性问题:

在java内存模型中规定,所有的变量都放在主内存中,当使用变量时,会把主内存中的变量复制到线程自己的工作空间或叫工作内存中,线程读写时操作的是自己工作内存中的变量。

如上图所示是一个双核的cpu系统架构,每个核都有自己的控制器和运算器,有自己的L1级缓存,有些架构里还有一个所有核共享的L2级缓存,那么java内存模型里的共享内存就是这里的L1或L2级缓存或者cpu寄存器。

当一个线程操作共享变量时,先将共享变量复制到自己的内存空间,处理完之后更新到主内存。当两个不同的线程同时操作一个共享变量时,由于缓存更新滞后,就导致了内存不可见问题。

关于synchronized关键字:

1.这是java提供的一个原子性内置锁,也叫监视器锁,每个对象都可以把它当成同步锁来用。

这个内置锁是一种排它锁,当一个线程获取到锁后,其他线程必须等待释放锁之后才能获取。

2.由于java中的线程与操作系统的原生线程一一对应,所有当阻塞一个线程时,需要由用户态切换到内核态执行阻塞操作,这是很耗时的操作,而synchronized就会导致上下文切换。

3.sychronized的内存语义:进入sychronized的内存语义是把sychronized块内的变量从工作内存中清除,直接从主内存中拿,而退出sychronized的语义是把修改后的变量刷新到主内存中。这也是加锁和释放锁的语义,加锁会清空锁内在工作内存中的共享变量,并加载主内存中的共享变量,释放锁会将共享变量刷新到主内存中。

关于volatile关键字:

上面介绍使用锁的方式可以解决内存可见性的问题,但是使用锁太笨重,因为它会带来线程上下文的切换开销,对于内存可见性的问题,java提供了volatile关键字的方法,它可以确保一个变量的更新对其他线程立马可见。一般在什么时候使用这个关键字呢:

l 写入变量值不依赖变量的当前值。如果依赖于当前值,将变成获取-计算-写入三步操作,这三步不是原子性的,而volatile不保证原子性。

l 读写变量值时没有加锁。因为加锁操作已经保证了内存可见性,不需要再声明volatile。

l Java内存模型允许编译器和处理器对指令重排序以提高程序运行性能,这在单线程下是可以的,在多线程下就会存在问题,通过添加volatile关键字可以禁用重排序。

Java中的原子性操作:

所谓原子操作,就是执行一系列操作时,要么全部执行,要么全部失败,不存在执行一部分的情况。

CAS操作:锁在并发中使用很广泛,但锁有一个不好的地方,当一个线程没有获取到锁时会被阻塞挂起,这会导致线程上下文的切换和重新调度开销。即使使用volatile也只是保证了可见性,并不能保证原子性。CAS(Compare and Swap)是jdk提供的非阻塞原子性操作,它通过硬件保证了比较-更新操作的原子性。

伪共享:

现代cpu为了解决cpu和内存之间速度差的问题,往往会在两者之间添加一级或多级缓存(cache),这个cache一般是集成到cpu内部的,也叫cpu cache,在cache内部是按行存储的,其中每一行称为一个cache行,这是cache与主内存进行数据交换的单位,一般为2的幂次方字节大小。

由于存放在cache行中的是内存块而不是变量,所以可能会把多个变量存放在一个cache行中,当多个线程同时修改一个cache行中的多个变量时,由于同时只能有一个线程操作cache行,所以相比将每个变量放到不同的cache行,性能会有所下降,这就是伪共享。

由于cache行中存放的都是内存连续的多个变量,因此在java8之前,一般都是字节填充的方式来避免该问题,填满一个cache行来解决问题,在java8中可以使用@sun.misc.Contended注解来解决伪共享问题。

锁个概述:

  1. 乐观锁和悲观锁

乐观锁和悲观锁是在数据库中引入的名词,但是在并发包锁里面也引入了类似的思想。

悲观锁是指数据对外界修改持保守态度,认为数据很容易被其他线程修改,所以在数据处理前先加锁,并在整个数据处理过程中,使数据处于锁定状态。悲观锁的实现往往依靠数据库的锁机制,在数据库中,对数据记录操作前先加上排它锁,如果获取锁失败,说明数据正在被其他线程修改。如果获取锁成功,则对数据修改然后提交事务并释放排它锁。

栗子:

//使用悲观锁获取指定记录

Select * from table1 where id =#{id} for update

//修改数据记录

.......

//update操作

Update table1 set name = #{name},age=#{age} where id=#{id}

只有一个线程能执行select成功,其他线程都被阻塞,一直到update操作完并提交事务成功后,悲观锁才会释放。

乐观锁是相对于悲观锁来说的,它认为数据在一般情况下不会有冲突,因此在访问数据记录前不会加排它锁,而是在提交数据更新的时候,才会正式对冲突进行检测,也就是说让用户根据返回的更新条数判断时候修改成功。上述代码改为乐观锁为:

//使用乐观锁获取指定内容

Select * from table2 where id= #{id}

//修改数据记录内容

........

//update操作

Update table2 set name = #{name} , age = #{age}, version=#{version}+1 where id=#{id} and version = #{version}

多个线程可以同时执行查询操作获取数据,并在各自线程栈上修改,在执行提交时,每个提交都加上了version字段, set中多了vesion+1的操作,如果数据没有变,则在原来的基础上增加一个版本号,否则就返回0条更新记录,有点CAS的意思,乐观锁直到提交时才锁定,因此不会有死锁问题。

  1. 公平锁和非公平锁

根据线程获取锁的抢占机制,锁可以分为公平锁和非公平锁,所谓公平锁就是多个线程获取锁的顺序是按照请求的时间早晚决定,也就是先来后到的顺序,而非公平锁则是随机性调度,不严格遵从时间顺序。由于公平锁会带来性能开销,应该优先使用非公平锁。

  1. 独占锁和共享锁

根据锁只能被单个线程持有还是能被多个线程持有,锁可以分为独占锁和共享锁。

  1. 可重入锁

当一个线程获取一个被其他线程持有的独占锁时,会被阻塞,但如果获取的是被自己占有的锁时呢?如果不被阻塞,则说这个锁是可重入锁,否则就是不可重入锁。Synchronized也是一种可重入锁。

  1. 自旋锁

由于java线程和操作系统的线程是一一对应的,所以当一个线程获取锁失败后,会被切换到内核态挂起。获取到锁之后又要切换到用户态,这样的切换的性能开销是比较大的,也会影响并发性能。而自旋锁则是,在发现锁被其他线程占用时,不会马上阻塞自己,在不放弃cpu执行权的情况下,多次尝试获取(默认是10次,在-XX:PreBlockSpinsh设置),很有可能在之后几次尝试中获取到了锁,如果没有,仍然进入阻塞状态被挂起。

原文地址:https://www.cnblogs.com/loveBolin/p/10200454.html

时间: 2024-11-07 13:32:38

锁机制概述的相关文章

数据库锁机制(一)——概述

注:内容为自己的推理认知+网络,如有错误和不合理之处,敬请指出. 在多线程环境中我用使用线程锁处理并发问题,而在数据库系统中,并发问题可以细化到事务级别,而DBMS对此的处理方案就是使用锁. 为了适应不同的需求,完善的DBMS对于锁的粒度划分应该是细粒度的,比如行锁.页锁.表锁.数据库锁等. 被锁定的数据对象的表现行为当然和未被锁定的数据对象不同,有的锁可以指示已锁定数据对于其他事务只可读.不可修改,有的锁指示已锁定数据数据对于其他事务既不可读也不可写. 如何使用锁呢?DBMS提供给了我们可以直

mysql的锁机制

锁概述 mysql锁机制的特点: 不同存储引擎支持不同的锁机制. MyISAM和MEMORY存储引擎支持表级锁: BDB存储引擎采用页面锁: InnoDB存储引擎支持行级锁. 表级锁: 开销小,加锁快,不会出现死锁,锁定粒度大,加锁冲突概率最高,并发度最低: 适用于以查询为主,只有少量按索引条件更新数据的应用,如Web应用: 行级锁 开销大,加锁慢,会出现死锁,锁定粒度小,发生锁冲突的概率最低,并发度最高: 适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(

SQL Server 内存中OLTP内部机制概述(一)

----------------------------我是分割线------------------------------- 本文翻译自微软白皮书<SQL Server In-Memory OLTP Internals Overview>:http://technet.microsoft.com/en-us/library/dn720242.aspx ----------------------------我是分割线------------------------------- SQL S

mysql锁机制(转载)

锁是计算机协调多个进程或线程并发访问某一资源的机制 .在数据库中,除传统的 计算资源(如CPU.RAM.I/O等)的争用以外,数据也是一种供许多用户共享的资源.如何保证数据并发访问的一致性.有效性是所有数据库必须解决的一 个问题,锁冲突也是影响数据库并发访问性能的一个重要因素. 从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂.本章我们着重讨论MySQL锁机制 的特点,常见的锁问题,以及解决MySQL锁问题的一些方法或建议. MySQL锁概述相对其他数据库而言,MySQL的锁机制比较简单,

SQL Server 内存中OLTP内部机制概述(二)

----------------------------我是分割线------------------------------- 本文翻译自微软白皮书<SQL Server In-Memory OLTP Internals Overview>:http://technet.microsoft.com/en-us/library/dn720242.aspx 译者水平有限,如有翻译不当之处,欢迎指正. ----------------------------我是分割线---------------

SQL Server 内存中OLTP内部机制概述(三)

----------------------------我是分割线------------------------------- 本文翻译自微软白皮书<SQL Server In-Memory OLTP Internals Overview>:http://technet.microsoft.com/en-us/library/dn720242.aspx 译者水平有限,如有翻译不当之处,欢迎指正. ----------------------------我是分割线---------------

JUC锁机制

互斥锁ReentrantLock 概述 ReentrantLock是一个可重入的互斥锁,又被称为"独占锁". 顾名思义,ReentrantLock锁在同一个时间点只能被一个线程锁持有:而可重入的意思是,ReentrantLock锁,可以被单个线程多次获取. ReentrantLock分为"公平锁"和"非公平锁".它们的区别体现在获取锁的机制上是否公平."锁"是为了保护竞争资源,防止多个线程同时操作线程而出错,Reentrant

数据库并发事务控制四:postgresql数据库的锁机制二:表锁

在博文<数据库并发事务控制四:postgresql数据库的锁机制 > http://blog.csdn.net/beiigang/article/details/43302947 中后面提到: 常规锁机制可以参考pg的官方手册,章节和内容见下面 13.3. Explicit Locking http://www.postgresql.org/docs/9.4/static/explicit-locking.html 这节分为:表锁.行锁.页锁.死锁.Advisory锁(这个名字怎么翻译好???

SQL Server 内存中OLTP内部机制概述(四)

----------------------------我是分割线------------------------------- 本文翻译自微软白皮书<SQL Server In-Memory OLTP Internals Overview>:http://technet.microsoft.com/en-us/library/dn720242.aspx 译者水平有限,如有翻译不当之处,欢迎指正. ----------------------------我是分割线---------------