一:synchronized会隐式的进行获取锁与释放锁的操做,新JDK中底层也是由CAS实现的java
对synchronized JVM 经过moniterenter和moniterexit命令包裹synchronized范围代码实现,隐式获取锁与释放锁对应的指令,这两个指令划分了一片同步块,具备排他性,当有线程进入该同步块后,其余线程必须等待在monitereneter指令上,直到进入同步块的线程经过moniterexit指令退出后,其余线程才能够进入同步块。面试
从本质上来讲,moniterenter与moniterexit是一组排他的对某一对象监视器进行尝试获取的过程(该对象正是示例中的lock),同一时刻只有一个线程成功获取对象监视器。在线程运行到同步块时,会经过moniterenter指令尝试获取对象的监视器,若是获取成功,则进入同步块,执行同步块内指令,若是获取失败,则会进入同步队列(SynchronizedQueue)中进行等待,线程当前状态变为BLOCK(阻塞)状态,直到有线程释放监视器。数据库
二:重入锁(ReentrantLock)安全
synchronized/ReentrantLock的区别:多线程
ReentrantLock提供了显式加解锁操做。提供了lock(),unlock()方法进行加解锁的操做,而synchronized是隐式进行加锁与解锁操做(依赖于编译器将其编译为moniterenter与moniterexit)。并发
对锁的等待能够中断,在持有锁的线程长时间不释放锁时,等待锁的线程能够选择放弃等待,这样就避免了synchronized可能带来的死锁问题。ReentrantLock.tryLock()能够设置等待时间。函数
ReentrantLock提供了公平锁与非公平锁,而synchronized的实现是非公平锁spa
可以更加精细的控制多线程的休眠与唤醒。对于同一个锁,咱们能够建立多个Condition,在不一样的状况下使用不一样的Condition。
例如,假如多线程读/写同一个缓冲区:当向缓冲区中写入数据以后,唤醒"读线程";当从缓冲区读出数据以后,唤醒"写线程";而且当缓冲区满的时候,"写线程"须要等待;当缓冲区为空时,"读线程"须要等待。 .net
若是采用Object类中的wait(), notify(), notifyAll()实现该缓冲区,当向缓冲区写入数据以后须要唤醒"读线程"时,不可能经过notify()或notifyAll()明确的指定唤醒"读线程",而只能经过notifyAll唤醒全部线程(可是notifyAll没法区分唤醒的线程是读线程,仍是写线程)。 可是,经过Condition,就能明确的指定唤醒读线程。线程
读写分离锁ReentrantReadWriteLock
ReentrantLock是对象,比synchronized须要更多的内存消耗。
ReentrantLock相对于synchronized来讲通常用于,加锁与解锁操做须要分离的使用场景,例如加解锁再也不一个函数里(synchronized没法用括号包围),相对来讲ReentrantLock提供了更高的灵活性,可是使用时必定不要忘了释放锁。
三:读写锁(ReentrantReadWriteLock)
相对与ReentrantLock它对变量的读写操做进行了区别对待,遵循如下特性:
在没有写操做线程获取写锁的状况下,全部读操做均可以获取读锁。
在有写线程获取写锁的状况下,读操做等待写线程释放锁后,才能够获取读锁。
在有读线程获取读锁的状况下,写线程会等待全部读线程释放锁后,才能够获取写锁,而且与此同时,全部的读锁也不可获取。
读不阻塞写的实现思路:
在读的时候若是发生了写,则应当重读而不是在读的时候直接阻塞写!
使用StampedLock就能够实现一种无障碍操做,即读写之间不会阻塞对方,可是写和写之间仍是阻塞的!
http://blog.csdn.net/sunfeizhi/article/details/52135136
综上所述,就是保证读写不会同时发生。下面咱们经过开发一个线程安全的读写分离HashMap来看看读写锁的具体使用:
public class ReentrantReadWriteLockHashMap {
private final Map<String, Object> hashMap = new HashMap<String, Object>();
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
public final void put(String key, Object value){
//上写锁,不容许其余线程读也不容许写
writeLock.lock();
hashMap.put(key, value);
writeLock.unlock();
}
public final Object get( String key ){
//上读锁,其余线程只能读不能写
readLock.lock();
Object value = hashMap.get(key);
readLock.unlock();
return value;
}
}
经过示例代码,能够看到读写锁的使用仍是很简单的,关键要理解读写锁的运行机制,读写分离,区别对待。
产生死锁的四个必要条件:
互斥条件:一个资源每次只能被一个线程/进程使用。
请求与保持条件:一个线程/进程因请求资源而阻塞时,对已得到的资源保持不放。
不剥夺条件:线程/进程已得到的资源,在未使用完以前,不能强行剥夺。
循环等待条件:若干线程/进程之间造成一种头尾相接的循环等待资源关系。
死锁的解除与预防
通常解决死锁的途径分为死锁的预防,避免,检测与恢复这三种。
死锁的预防是要求线程/进程申请资源时遵循某种协议,从而打破产生死锁的四个必要条件中的一个或几个,保证系统不会进入死锁状态。
死锁的避免不限制线程/进程有关申请资源的命令,而是对线程/进程所发出的每个申请资源命令加以动态地检查,并根据检查结果决定是否进行资源分配。
死锁检测与恢复是指系统设有专门的机构,当死锁发生时,该机构可以检测到死锁发生的位置和缘由,并能经过外力破坏死锁发生的必要条件,从而使得并发进程从死锁状态中恢复出来。
对于java程序来讲,产生死锁时,咱们能够用jvisualvm/jstack来分析程序产生的死锁缘由,根据病因来根治。
一道面试题比较synchronized和读写锁
第二个if比较关键,它避免了多余的几回对数据库的读取 ,
在lock里加if避免多线程排队去db取数据,加了if后面阻塞的线程再拿到锁后判断就不为null,直接返回结果