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解决的是多个线程之间的访问资源的同步性

 

猜你喜欢

转载自agilestyle.iteye.com/blog/2356898