Java核心篇之Java锁–day2
- 乐观锁:乐观锁是一种乐观思想,即认为读多写少,每次去取数据的时候都认为其他人不会修改,所以不会上锁;但是在更新的时候会判断一下在此期间别人有没有去修改它,如果有人修改的话,就需要重新尝试。实现的方式主要有以下两种:
1) 版本号机制:一般是在数据表中加上一个数据版本号的version字段,表示数据被修改的次数,当数据被修改时,版本号就会+1,在提交更新时,若刚才读取到的version值与当前数据库中version的值相等时才进行更新,否则重试更新操作,直到更新成功。
2)CAS:compare and swap
CAS涉及到三个操作数:
V:需要读写的内存值
A:进行比较的值
B:拟写入的新值
当且仅当V=A时,CAS通过原子方式用新值B来代替V,否则不会执行任何操作(比较和替换是一个原子操作),一般情况是自旋操作
- 乐观锁缺点:ABA问题,循环时间长开销大
-
悲观锁:是一种悲观思想,即认为写多,读少,每次去取数据的时候都认为别人会修改,所以每次读数据的时候都会加锁,这样别人想拿到这个数据的话就会一直block直至拿到锁。Java的悲观锁synchronized。
-
自旋锁:如果持有锁的线程能在很短时间内释放资源,那么等待竞争锁的线程就不需要进入阻塞挂起状态,他们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取到锁,这样能够避免用户上下文切换所带来的消耗。
-
synchronized同步锁,它属于悲观锁,同时也属于可重入锁。
-
synchronized与reentrantLock的对比:
1、ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了定时锁等候和中断锁等候
线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定, 如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断;
如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情
ReentrantLock获取锁定与三种方式:
a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
c)tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断
2、synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中
3、在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态; -
都是可重入锁,
可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。 -
可中断锁:在Java中,synchronized就不是可中断锁,而Lock是可中断锁。
如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。
在前面演示lockInterruptibly()的用法时已经体现了Lock的可中断性。
6.公平锁:
公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。
非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。
synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。
而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。