欢迎来到 IT实训基地-南通科迅教育
咨询电话:0513-81107100
java中锁的总结
2017/4/7
南通科迅教育
383
南通Web前端培训怎么样

java中锁的总结:根据Java的内存分布,知道栈内存是线程私有的,也就是说此内存只能被当前线程访问,其他线程不可见。而堆内存是线程共享的,理论上能够北多个线程访问到。如果共享资源能够被多个线程访问,为了保证每个线程在使用资源的过程中不被其他线程修改,需要对资源加锁。

锁的目的就是保证资源被线程占用的过程中不会被其他的线程修改。

二、线程获取锁失败

线程获取锁失败后等待,大体上可以分为两种:自旋等待和阻塞等待。

自旋等待:线程获取锁失败后,程序还是会执行调度,处理器还是会执行程序的指令,但是指令的表达是不断尝试获取锁,所以自旋等待也叫忙等待。

阻塞等待:线程获取锁失败后,处理器被切换到别的程序上去,一直到其他程序释放了锁,将它唤醒,才会再次尝试获取锁。

延伸:
Java线程一般都会映射到操作系统的进程,比如在Linux平台,Java线程会映射到Linux的线程(轻量级进程)。在Linux内核中,进程会由调度器来进行调度。这里简单说下CFS调度器,系统中所有可执行的进程在linux内核中组成一棵红黑树,CFS会从红黑树选择进程来调度。当进程阻塞后,会被从红黑树中转移到等待队列中,直到进程被唤醒,再次从等待队列中转移到红黑树中,才有可能再次被调度。

三、Java中的锁

1.6版本后,Java提供了两种锁:内置锁和显示锁

1、内置锁synchronized
synchronized是Java的关键字,是jvm层面的锁。对于synchronized语句,当Java源代码被javac编译成bytecode的时候,会在同步块的入口位置和退出位置分别插入monitorenter和monitorexit字节码指令。而synchronized方法会被翻译成普通的方法调用,在JVM字节码层面并没有任何特别的指令来实现被synchronized修饰的方法,只是在Class文件的方法表中将该方法的access_flags字段中synchronized标志位置1,表示该方法所属对象或者所属的Class在JVM作为所对象。

1)JVM中的锁流程

在JVM中,monitorenter和monitorexit字节码依赖于底层的操作系统的Mutex Lock来实现的,使用Mutex Lock需要将当前线程挂起并从用户态切换到内核态来执行,这种切换的代价是非常昂贵的,严重影响系统性能。jdk1.6对锁进行了优化,如偏向锁、轻量级锁、锁粗化、锁消除、适应性自旋等减少锁的开销。

偏向锁:即锁会偏向于当前已经占有锁的线程 。在无竞争时,之前获得锁的线程再次获得锁时,会判断是否偏向锁指向我,那么该线程将不用再次获得锁,直接就可以进入同步块。
轻量级锁:本意是为了减少多线程进入互斥的几率,并不是要替代互斥。它利用了CPU原语Compare-And-Swap(CAS,汇编指令CMPXCHG),尝试在进入互斥前,进行加锁操作,尽量不要用操作系统层面的互斥,提高了性能。
自旋锁:当竞争存在时,因为轻量级锁尝试失败,让线程做几个空操作(自旋),并且不停地尝试拿到这个锁(类似tryLock),当循环次数达到以后,仍然升级成重量级锁。
小结:偏向锁,轻量级锁,自旋锁都不是在Java语言层面的锁优化,而是在JVM当中。
首先偏向锁是为了避免某个线程反复获得/释放同一把锁时的性能消耗,如果仍然是同个线程去获得这个锁,尝试偏向锁时会直接进入同步块,不需要再次获得锁。
如果竞争存在,膨胀为轻量级锁,尝试使用CAS操作来获得锁,如果轻量级锁获得失败,说明存在竞争。但是也许很快就能获得锁,就会尝试自旋锁,将线程做几个空循环,每次循环时都不断尝试获得锁。如果自旋锁也失败,那么只能升级成重量级锁。

整个synchronized锁流程如下:

检测Mark Word里面是不是当前线程的ID,如果是,表示当前线程处于偏向锁;

如果不是,则使用CAS将当前线程的ID替换Mard Word,如果成功则表示当前线程获得偏向锁,置偏向标志位1;

如果失败,则说明发生竞争,撤销偏向锁,进而升级为轻量级锁。

当前线程使用CAS将对象头的Mark Word替换为锁记录指针,如果成功,当前线程获得锁

如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。

如果自旋成功则依然处于轻量级状态,如果自旋失败,则升级为重量级锁。

2)jvm锁具体表现

在JVM中创建对象时会在对象前面加上两个字大小的对象头,在32位机器上一个字为32bit,根据不同的状态位Mark World中存放不同的内容。Mark Word被分成两部分,刚开始时LockWord为被设置为HashCode、最低三位表示LockWord所处的状态,初始状态为001表示无锁状态。线程如何获取对象的锁?

Monitor Record是线程私有的数据结构,每一个线程都有一个可用monitor record列表,同时还有一个全局的可用列表,每一个被锁住的对象都会和一个monitor record关联。对象头中的LockWord指向monitor record的起始地址,由于这个地址是8byte对齐的所以LockWord的最低三位可以用来作为状态位。同时monitor record中有一个Owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。

一个线程能够通过两种方式锁住一个对象:1、通过膨胀一个处于无锁状态(状态位001)的对象获得该对象的锁;2、对象已经处于膨胀状态(状态位00)但LockWord指向的monitor record的Owner字段为NULL,则可以直接通过CAS原子指令尝试将Owner设置为自己的标识来获得锁。

2、显示锁

JDK5提供的锁工具类都在java.util.concurrent.locks包下,有Condition、Lock、ReadWriteLock等接口:

Lock的实现包括DummyConcurrentLock, NonReentrantLock, ReadLock, WriteLock, ReentrantLock;

ReadWriteLock的实现类有ReentrantReadWriteLock。

Condition实例实质上被绑定到一个锁上。

3、按照锁的功能和性质,Java中的锁又可以分为:

公平锁和非公平锁:公平锁是指多个线程在等待同一个锁时,必须按照申请锁的先后顺序来一次获得锁。

自旋锁:

可重入锁:可重入锁,也叫做递归锁,指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。

类锁和对象锁:类锁:在方法上加上static synchronized的锁,或者synchronized(xxx.class)的锁。

悲观锁和乐观锁:悲观锁是假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。 乐观锁是假定不会发生并发冲突,只在提交操作时检测是否违反数据完整性。

共享锁和排它锁:共享锁是如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排它锁。获准共享锁的事务只能读数据,不能修改数据。 排它锁是如果事务T对数据A加上排它锁后,则其他事务不能再对A加任何类型的锁。获得排它锁的事务即能读数据又能修改数据。

读写锁:读写锁是一个资源能够被多个读线程访问,或者被一个写线程访问但不能同时存在读线程。Java当中的读写锁通过ReentrantReadWriteLock实现。具体使用方法这里不展开。

互斥锁:所谓互斥锁就是指一次最多只能有一个线程持有的锁。在JDK中synchronized和JUC的Lock就是互斥锁。

分段锁:ConcurrentHashMap中采用了分段锁

闭锁:闭锁是一种同步工具类,可以延迟线程的进度直到其到达终止状态。闭锁的作用相当于一扇门:在闭锁到达结束状态之前,这扇门一直是关闭的,并且没有任何线程能通过,当到达结束状态时,这扇门会打开允许所有的线程通过。当闭锁到达结束状态后,将不会再改变状态,因此这扇门将永远保持打开状态。闭锁可以用来确保某些活动指导其他活动都完成后才继续执行。CountDownLatch就是一种灵活的闭锁实现。

77
关闭
先学习,后交费申请表
每期5位名额
在线咨询
免费电话
QQ联系
先学习,后交费
TOP
您好,您想咨询哪门课程呢?
关于我们
机构简介
官方资讯
地理位置
联系我们
0513-91107100
周一至周六     8:30-21:00
微信扫我送教程
手机端访问
南通科迅教育信息咨询有限公司     苏ICP备15009282号     联系地址:江苏省南通市人民中路23-6号新亚大厦三楼             法律顾问:江苏瑞慈律师事务所     Copyright 2008-