Concurrent - 锁
原创转载请注明出处:http://agilestyle.iteye.com/blog/2356898
什么是可重入锁(ReentrantLock)?
Java多线程中可以使用synchronized关键字来实现线程之间的同步互斥,但在JDK1.5中新增加了ReentrantLock类也能达到同样的效果,并且在扩展功能上也更加强大,比如 嗅探锁定、多路分支通知等功能
ReentrantLock并不是替代synchronized的方法,而是当synchronized不适用时,作为一种可选的高级功能;
从代码上尽量用synchronized,jvm会对synchronized做一定的优化,并且代码可维护和稳定。只有在需要ReentrantLock的一些特性时,可以考虑用ReentrantLock实现。
当一个线程进入某个对象的一个synchronized的实例方法后,其它线程是否可进入此对象的其它方法?
可进入非synchronized方法
synchronized和java.util.concurrent.locks.Lock的异同?
- synchronized相当于整个Lock对象中只有一个单一的Condition对象,所有的线程都注册在它一个对象身上;synchronized无法中断一个正在等待获得锁的线程,也即多线程竞争一个锁时,其余未得到锁的线程只能不停的尝试获得锁,而不能中断。这种情况对于大量的竞争线程会造成性能的下降等后果。
- Lock对象可以创建多个Condition(即对象监视器)实例,线程对象可以注册在指定的Condition中,从而可以有选择性地进行通知,在调度线程上更加灵活。
所以在synchronized无法满足需求的情况下,Lock可以作为一种高级工具,这些功能包括“可定时的、可轮询的与可中断的锁获取操作,公平队列,以及非块结构的锁”否则还是优先使用synchronized
乐观锁和悲观锁的理解及如何实现,有哪些实现方式?
乐观锁 —— CAS
悲观锁 —— mutex
Note:
数据库方面
乐观锁 —— version字段
悲观锁 —— for update
如何实现乐观锁(CAS)?如何避免ABA问题?
ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。
ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A - 2B-3A。
从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
http://ifeve.com/atomic-operation/
读写锁可以用于什么应用场景?
http://ifeve.com/read-write-locks/
什么时候应该使用可重入锁?
若一个程序或子程序可以“安全的被并行执行(Parallel computing)”,则称其为可重入(reentrant或re-entrant)的。即当该子程序正在运行时,可以再次进入并执行它(并行执行时,个别的执行结果,都符合设计时的预期)。可重入概念是在单线程操作系统的时代提出的。
场景1:如果发现该操作已经在执行中则不再执行(有状态执行)
private ReentrantLock lock = new ReentrantLock();
...
if (lock.tryLock()) { // 如果已经被lock,则立即返回false不会等待,达到忽略操作的效果
try {
// 操作
} finally {
lock.unlock();
}
}
场景2:如果发现该操作已经在执行,等待一个一个执行(同步执行,类似synchronized)
private ReentrantLock lock = new ReentrantLock(); // 参数默认false,不公平锁
private ReentrantLock lock = new ReentrantLock(true); // 公平锁
...
try {
lock.lock(); // 如果被其它资源锁定,会在此等待锁释放,达到暂停的效果
// 操作
} finally {
lock.unlock();
}
场景3:如果发现该操作已经在执行,则尝试等待一段时间,等待超时则不执行(尝试等待执行)
private ReentrantLock lock = new ReentrantLock(); // 参数默认false,不公平锁
...
try {
if (lock.tryLock(5, TimeUnit.SECONDS)) { // 如果已经被lock,尝试等待5s,看是否可以获得锁,如果5s后仍然无法获得锁则返回false继续执行
try {
// 操作
} finally {
lock.unlock();
}
}
} catch (InterruptedException e) {
e.printStackTrace(); // 当前线程被中断时(interrupt),会抛InterruptedException
}
场景4:如果发现该操作已经在执行,等待执行。这时可中断正在进行的操作立刻释放锁继续下一操作
private ReentrantLock lock = new ReentrantLock(); // 参数默认false,不公平锁
...
try {
lock.lockInterruptibly();
//操作
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
什么场景下可以使用volatile替换synchronized?
关键字volatile的主要作用是使变量在多个线程间可见,但无法保证原子性,对于多个线程访问同一个实例变量需要加锁进行同步。
volatile和synchronized区别:
- volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰变量,synchronized可以修饰方法,以及代码块(JVM新版本发布,synchronized关键字在执行效率上有大提升,在开发中使用synchronized关键字的比率还是比较大的)
- 多线程访问volatile不会发生阻塞,而synchronized会出现阻塞
- volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存的数据做同步
- volatile解决的是变量在多个线程之间的可见性;而synchronized解决的是多个线程之间的访问资源的同步性