参考:
乐观锁和悲观锁
宏观概念上,锁分为悲观锁和乐观锁。
乐观锁:乐观思想,认为读多写少,并发概率低,每次写操作不上锁,但在更新时会先读出当前版本号,然后加锁操作(如果跟上一次的版本号相同,则更新),如果失败,就要重复“读-比较-写”操作。Java里的乐观锁基本都是通过CAS操作实现;轻量锁、偏向锁和自旋锁都是乐观锁。
悲观锁:悲观思想,认为写多读少,并发概率高,每次读写数据都会上锁,这样别人想读写这个数据就会block直到拿到锁。java中的悲观锁就是Synchronized;
Java线程阻塞的代价
Java线程是映射到操作系统原生线程之上的,如果要阻塞或缓行一个线程就需要操作系统介入,需要在用户态和内核态之间切换,会消耗大量的系统资源,因为用户态和和内核态都有各自专用的内存空间,专用的寄存器等。
- 如果线程状态切换时一个高频操作时,这将消耗很多CPU处理时间;
- 对于那些需要同步的简单的代码块,获取锁挂起操作消耗的时间比用户代码执行的时间还要长,这种同步策略显然非常糟糕;
synchronized
会导致未能获取锁的线程进入阻塞状态,所以他是Java语言中一个重量级的同步操作,被称为重量级锁。
非公平锁,无法保证等待的线程获取锁的顺序。新的线程会先自旋尝试获取锁,如果锁正在被占用,则该线程进入ContentionList。如果锁正好被释放,有可能获取到锁。
synchronized示意图自旋锁:
自旋锁适用于锁竞争不激烈的情况,使等待锁的线程处于自旋状态等待锁释放,但会占用cpu做无用功(相比于阻塞挂起的操作消耗资源较小);自旋锁的时间在jdk1.5中是写死的,但在jdk1.6中,由前一次同一个锁上的自旋时间以及锁的拥有者的状态来决定的;
时间阈值:一般为一个上下文切换的时间。
竞争切换:自旋锁是不公平的,当owner释放锁(unlock),不会将锁直接交给onDeck,onDeck需要重新竞争锁,缺点:牺牲公平性,优点:提高系统吞吐量。
在JVM中也把这种行为称为”竞争切换“。
偏向锁:
是Java6引入的一项多线程优化。它会偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,在这种情况下,就会给线程加一个偏向锁;如果运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM消除他身上的偏向锁,将锁恢复到标准的轻量级锁;
synchronized锁流程偏向锁的释放:不主动释放,但在遇到竞争的情况下,在达到safe point(此时无字节码正在执行)就会释放偏向锁,mark word回复到”00“状态。
其它
死锁
必须满足一下4个条件:
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
网友评论