1、JavaSE5的synchronized都是重量级的,而在JavaSE5之后出现了Lock接口以及相关接口来实现锁的功能。而且在JavaSE6 中Lock性能和Lock性能相差无几,但是synchronized能实现的功能Lock都能实现,原因是Lock功能更全面。
2、Lock接口提供的synchronized关键字不具备的主要特性有
1)尝试非阻塞地获取锁,通过 tryLock 进行非阻塞的尝试获取锁
2)能被中断地获取锁,通过lockInterruptibly 进行可中断的竞争锁,在锁竞争过程中可以被中断
3)超时获取锁,通过在tryLock获取锁时设置最大超时时间进行超时获取
4)必须手工释放锁,因为synchronized代码块中发生异常会自动释放锁,但是Lock则不会。
5)获取等待通知组件,通过调用 newCondition ,而且可以有多个Condition.
3、使用Lock的伪代码
Lock lock = new ReentrantLock();
lock.lock();
try{
// do something.
}finally{
lock.unlock();
}
1)注意必须在finally里面释放锁,原因是Lock不会自动释放
2)获取锁的过程最好写在try外面,原因是自定义的Lock可能在初始化Lock时发生异常,可能这个时候都还没有获取到锁,然后又调用释放锁,就会出现代码异常。
4、重入锁(ReentrantLock)
重入锁是指支持重进入的锁,也就是获取锁之后继续获取该锁是不会被阻塞。synchronized隐式含有重入锁,所以可以递归调用synchronized修饰的方法也不会被阻塞等待。
ReetrantLock 还可以支持公平锁和非公平锁,通过构造方法Boolean参数决定。
公平锁是指先对锁进行请求的锁一定先被满足。这意味着等待时间最长的线程最有可能获取到锁,这样可以避免活锁的发生。如果不管线程等待的时间任意分配锁则是非公平锁。
公平锁和非公平锁相对,公平性虽然保证了锁的获取按照了FIFO原则,而代价则是进行大量的线程切换,非公平锁虽然可能造成线程出现活锁的情况,但是避免线程的切换可以保证更大的吞吐量。
5、读写锁(ReentrantReadWriteLock)
无论是synchronized还是ReentrantLock都是排他锁,在同一时刻只允许一个线程进行访问,而读写锁在同一时刻可以运行多个读线程同时访问,但是在写线程访问时,所有的读线程和其他写线程都被阻塞。
读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有很大的提升。
读写锁可以降级(从写锁降级为读锁),但是不可以升级(从读锁升级为写锁),为什么不能升级呢?是因为假设有多个线程同时获取了读锁,突然有一个线程升级为写锁,那么计算他修改了数据,但是其他拥有读锁的线程读取的数据可能不是最新的,避免脏读数据所以不支持锁的升级。
如果读远远大于写,那么读写锁可谓是最佳之选了,下面就是使用读写锁实现缓存的代码
private static Map<String,Object> cache = new HashMap<>();
private static ReadWriteLock lock = new ReentrantReadWriteLock();
private static Lock readLock = lock.readLock();
private static Lock writeLock = lock.writeLock();
public static Object get(String key){
readLock.lock();
try{
return cache.get(key);
}finally{
readLock.unlock();
}
}
public static void put(String key, Object value){
writeLock.lock();
try{
cache.put(key, value);
}finally{
writeLock.unlock();
}
}
6、LockSupport
LockSupport是JDK中比较底层的类,用来创建锁和其他同步工具类的基本线程阻塞原语。
java锁和同步器框架的核心 AQS: AbstractQueuedSynchronizer,就是通过调用
LockSupport.park() 和 LockSupport.unpark()实现线程的阻塞和唤醒的。
LockSupport是可不重入。
示例代码(一直阻塞在 start,除非被唤醒 ):
public static void main(String[] args) {
System.out.println("start");
LockSupport.park();
System.out.println("end.");
}