-
多线程分类的最新文章
-
最新评论
-
咖啡:加油~
-
wx5951c76fc3280:好的,谢谢支持
-
目录
-
Lock和synchronized这两个最常见的锁都可以达到线程安全的目的,但是功能上有很大不同。
Lock并不是用来代替synchronized的而是当使用synchronized不满足情况或者不合适的时候来提供高级功能的
为什么synchronized不够用
灵活性的提高带来了额外的责任。 缺少块结构锁定需要手动的去释放锁。 在大多数情况下,应使用以下惯用法:
Lock lock = new ReentrantLock();
lock.lock();
try{
}finally {
lock.unlock();
}
当锁定和解锁发生在不同的范围内时,必须小心以确保通过try-finally或try-catch保护持有锁定时执行的所有代码,以确保在必要时释放锁定。
Lock实现通过使用非阻塞尝试获取锁( tryLock() ),尝试获取可被中断的锁( lockInterruptibly以及尝试获取锁),提供了比使用synchronized方法和语句更多的功能。可能会超时( tryLock(long, TimeUnit) )。
Lock类还可以提供与隐式监视器锁定完全不同的行为和语义,例如保证顺序,不可重用或死锁检测。 如果实现提供了这种特殊的语义,则实现必须记录这些语义。
请注意, Lock实例只是普通对象,它们本身可以用作synchronized语句中的目标。 获取Lock实例的监视器锁与调用该实例的任何lock方法没有指定的关系。 建议避免混淆,除非在自己的实现中使用,否则不要以这种方式使用Lock实例。
所有Lock实现必须强制执行与内置监视器锁所提供的相同的内存同步语义,如Java语言规范中所述 :
不成功的锁定和解锁操作以及可重入的锁定/解锁操作不需要任何内存同步效果。
实施注意事项
锁获取的三种形式(可中断,不可中断和定时)在其性能特征可能有所不同。 此外,在给定的Lock类中,可能无法提供中断正在进行的锁定的功能。 因此,不需要为所有三种形式的锁获取定义完全相同的保证或语义的实现,也不需要支持正在进行的锁获取的中断。 需要一个实现来清楚地记录每个锁定方法提供的语义和保证。 在支持锁获取中断的范围内,它还必须服从此接口中定义的中断语义:全部或仅在方法输入时才这样做。
void lock(); // 获取锁。
注意:lock()方法不能被中断,这会带来很大的隐患:一旦陷入死锁、lock()就会陷入永久等待状态
void lockInterruptibly() throws InterruptedException;
除非当前线程被中断,否则获取锁。
获取锁(如果有)并立即返回。
如果该锁不可用,则出于线程调度目的,当前线程将被挂起,并在发生以下两种情况之一之前处于休眠状态:
如果当前线程:在进入此方法时已设置其中断状态;要么获取锁时被中断,并且支持锁获取的中断,然后抛出InterruptedException并清除当前线程的中断状态。
注意事项
在某些实现中,中断锁获取的能力可能是不可能的,并且如果可能的话可能是昂贵的操作。 程序员应意识到可能是这种情况。 在这种情况下,实现应记录在案。与正常方法返回相比,实现可能更喜欢对中断做出响应。Lock实现可能能够检测到锁的错误使用,例如可能导致死锁的调用,并且在这种情况下可能引发(未经检查的)异常。
注意 synchronized 在获取锁时是不可中断的
boolean tryLock();
非阻塞获取锁(如果有)并立即返回true值。 如果锁不可用,则此方法将立即返回false值。相比于Lock这样的方法显然功能更加强大,我们可以根据是否能获取到锁来决定后续程序的行为
注意:该方法会立即返回,即便在拿不到锁的时候也不会在一只在那里等待
该方法的典型用法是:
Lock lock = new ReentrantLock();
if(lock.tryLock()){
try{
// TODO
}finally {
lock.unlock();
}
}else{
// TODO
}
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
如果线程在给定的等待时间内获取到锁,并且当前线程尚未中断,则获取该锁。
如果锁可用,则此方法立即返回true值。 如果该锁不可用,则出于线程调度目的,当前线程将被挂起,并处于休眠状态,直到发生以下三种情况之一:
如果经过了指定的等待时间,则返回值false 。 如果时间小于或等于零,则该方法将根本不等待。
注意事项
在某些实现中,中断锁获取的能力可能是不可能的,并且如果可能的话可能是昂贵的操作。 程序员应意识到可能是这种情况。 在这种情况下,实现应记录在案。与正常方法返回或报告超时相比,实现可能更喜欢对中断做出响应。Lock实现可能能够检测到锁的错误使用,例如可能导致死锁的调用,并且在这种情况下可能引发(未经检查的)异常。
void unlock(); //释放锁。
注意事项
Lock实现通常会限制哪些线程可以释放锁(通常只有锁的持有者才能释放锁),并且如果违反该限制,则可能引发(未经检查的)异常。
Condition newCondition(); //返回绑定到此Lock实例的新Condition实例。
该组件与当前锁绑定,当前线程只有获得了锁。 才能调用该组件的wait()方法,而调用后,当前线程将释放锁。
注意事项
Condition实例的确切操作取决于Lock实现。
Lock对象锁还提供了synchronized所不具备的其他同步特性,如可中断锁的获取(synchronized在等待获取锁时是不可中断的),超时中断锁的获取,等待唤醒机制的多条件变量Condition等,这也使得Lock锁具有更大的灵活性。Lock的加锁和释放锁和synchronized有同样的内存语义,也就是说下一个线程加锁后可以看到前一个线程解锁前发生的所有操作。
根据一下6种情况可以区分多种不同的锁,下面详细介绍
是否锁住 | 锁名称 | 实现方式 | 例子 |
---|---|---|---|
锁柱 | 悲观锁 | synchronized、lock | synchronized、lock |
不锁住 | 乐观锁 | CAS算法 | 原子类、并发容器 |
悲观锁又称互斥同步锁,互斥同步锁的劣势:
悲观锁:
当一个线程拿到锁了之后其他线程都不能得到这把锁,只有持有锁的线程释放锁之后才能获取锁。
乐观锁:
自己才进行操作的时候并不会有其他的线程进行干扰,所以并不会锁住对象。在更新的时候,去对比我在修改期间的数据有没有人对他进行改过,如果没有改变则进行修改,如果改变了那就是别人改的那我就不改了放弃了,或者重新来。
开销对比:
使用场景:
是否共享 | 锁名称 |
---|---|
可以 | 共享锁(读锁) |
不可以 | 排他锁(独占锁) |
共享锁:
获取共享锁之后,可以查看但是无法修改和删除数据,其他线程此时也可以获取到共享锁也可以查看但无法修改和删除数据
案例:ReentrantReadWriteLock的读锁(具体实现后续系列文章会讲解)
排他锁:
获取排他锁的之后,别的线程是无法获取当前锁的,比如写锁。
案例:ReentrantReadWriteLock的写锁(具体实现后续系列文章会讲解)
是否排队 | 锁名称 |
---|---|
排队 | 公平锁 |
不排队 | 非公平锁 |
非公平锁:
先尝试插队,插队失败再排队,非公平是指不完全的按照请求的顺序,在一定的情况下可以进行插队
存在的意义:
案例:
例如:当有线程执行tryLock的时候一旦有线程释放了锁,那么这个正在执行tryLock的线程立马就能获取到锁即使在它之前已经有其他线程在等待队列中
公平锁:
排队,公平是指的是按照线程请求的顺序来进行分配锁
案例:以ReentrantLock为例,创建对象的时候参数为true(具体实现后续系列文章会讲解)
注意:
非公平也同样不提倡插队行为,这里指的非公平是指在合适的时机插队,而不是盲目的插队
优缺点:
非公平锁:
公平锁:
是否可以重入 | 锁名称 |
---|---|
可以 | 可重入锁 |
不可以 | 不可重入锁 |
案例:以ReentrantLock为例(具体实现后续系列文章会讲解)
是否可以中断 | 锁名称 | 案例 |
---|---|---|
可以 | 可中断锁 | Lock是可中断锁(因为tryLock和lockInterruptibly都能响应中断) |
不可以 | 不可中断锁 | Synchronized就是不可中断锁 |
是否自旋 | 锁名称 |
---|---|
是 | 自旋锁 |
否 | 阻塞锁 |
使用场景:
这三种锁优化的方式在前一篇Synchronized文章种所有讲解
1
收藏
Ctrl+Enter 发布
发布
取消