请用代码举例解释死锁和活锁之间的区别。
请用代码举例解释死锁和活锁之间的区别。
从http://en.wikipedia.org/wiki/Deadlock获取:
在并发计算中,死锁是一个状态,在这个状态下,一组操作的每个成员都在等待另一个成员释放锁。
活锁类似于死锁, 除了参与活锁的进程状态 相互之间不断变化,互相影响, 但是没有任何一个进程会向前推进。 活锁是资源饥饿的特例; 普遍定义只是说明了 一个特定的进程没有向前推进。
现实生活中的一个例子是, 当两个人在狭窄的走廊里遇到时, 每个人都试图礼貌地让对方先过去, 但他们最终左右摇摆而没有取得进展, 因为他们都反复地同时朝同一方向移动。
活锁是某些检测和 从死锁中恢复的算法面临的风险。 如果有多个进程采取行动, 死锁检测算法可能会一再被触发。 可以通过确保只有一个进程(随机选择或按优先级选择)采取行动来避免这种情况。
一个线程通常是响应另一个线程的操作。如果另一个线程的行动也是对另一个线程行动的响应,则可能导致活锁。
与死锁类似,活锁的线程无法继续向前进展。但是,线程不会被阻塞——它们只是忙于相互响应而无法恢复工作。这就像两个人试图在走廊里相互擦肩而过:Alphonse向左移以让Gaston通过,而Gaston向右移以让Alphonse通过。看到他们仍然相互阻挡,Alphonse向右移动,而Gaston向左移动。他们仍然相互阻拦,如此往复......
活锁和死锁的主要区别在于线程不会被阻塞,而是会不断尝试相互响应。
在这张图片中,两个圆圈(线程或进程)将尝试通过左右移动给彼此腾出空间。但是它们无法再移动更远。
这里的所有内容和示例都来自:
操作系统:内部原理和设计原则
William Stallings
第8版
死锁:两个或多个进程由于彼此等待对方执行某些操作而无法继续。
例如,考虑两个进程P1和P2,以及两个资源R1和R2。假设每个进程都需要访问两个资源才能执行其功能的一部分。那么可能出现以下情况:操作系统将R1分配给P2,将R2分配给P1。每个进程都在等待两个资源中的一个。在获得另一个资源并执行需要两个资源的功能之前,它们都不会释放已经拥有的资源。这两个进程就会陷入死锁。
活锁:两个或多个进程持续地改变其状态以响应其他进程的更改,而不执行任何有用的工作:
饥饿:一个可运行的进程被调度程序无限期地忽略;尽管它能够继续执行,但它从未被选择。
假设三个进程(P1、P2、P3)都需要定期访问资源R。考虑这样一种情况:P1拥有该资源,而P2和P3都被延迟了,正在等待该资源。当P1退出其关键部分时,应该允许P2或P3访问R。假设操作系统授予P3访问权限,并且在P3完成其关键部分之前,P1再次需要访问。如果操作系统在P3完成后授予P1访问权限,并交替授予P1和P3访问权限,则可能无限期地拒绝P2访问该资源,尽管不存在死锁情况。
附录A - 并发主题
死锁示例
如果两个进程在执行while语句之前都将它们的标志设置为true,那么每个进程都会认为另一个进程已经进入了其关键部分,导致死锁。
/* PROCESS 0 */
flag[0] = true; // <- get lock 0
while (flag[1]) // <- is lock 1 free?
/* do nothing */; // <- no? so I wait 1 second, for example
// and test again.
// on more sophisticated setups we can ask
// to be woken when lock 1 is freed
/* critical section*/; // <- do what we need (this will never happen)
flag[0] = false; // <- releasing our lock
/* PROCESS 1 */
flag[1] = true;
while (flag[0])
/* do nothing */;
/* critical section*/;
flag[1] = false;
活锁示例
/* PROCESS 0 */
flag[0] = true; // <- get lock 0
while (flag[1]){
flag[0] = false; // <- instead of sleeping, we do useless work
// needed by the lock mechanism
/*delay */; // <- wait for a second
flag[0] = true; // <- and restart useless work again.
}
/*critical section*/; // <- do what we need (this will never happen)
flag[0] = false;
/* PROCESS 1 */
flag[1] = true;
while (flag[0]) {
flag[1] = false;
/*delay */;
flag[1] = true;
}
/* critical section*/;
flag[1] = false;
[...] 考虑下面的事件序列:
这个序列可以无限地扩展,两个进程都不能进入其关键部分。严格来说,这不是死锁,因为两个进程的相对速度发生任何变化都会打破这个循环并允许一个进程进入临界区。这种情况被称为活锁。请记住,当一组进程希望进入其关键部分但没有进程能够成功时,会出现死锁。在活锁中,有可能存在一些执行序列成功进入临界区, 但也有可能描述一个或多个执行序列,其中没有任何进程进入其关键部分。
这段内容已经不再是书本上的了。
旋转锁又怎么样呢?
旋转锁是一种避免使用操作系统锁机制成本的技术。通常,您会执行以下操作:
try
{
lock = beginLock();
doSomething();
}
finally
{
endLock();
}
在beginLock()
的执行时间比doSomething()
更多时,问题就开始出现了。夸张地说,当beginLock
需要1秒,而doSomething
只需要1毫秒时,会发生什么呢?beginLock
会花费那么多时间?如果锁是自由的,它不会花费太多(参见https://dev59.com/AXA65IYBdhLWcg3wvxeE#49712993),但如果锁定未被释放,操作系统将“冻结”您的线程,设置一个机制,在锁定被释放时唤醒您,然后在未来再次唤醒您。void beginSpinLock(lock)
{
if(lock) loopFor(1 milliseconds);
else
{
lock = true;
return;
}
if(lock) loopFor(2 milliseconds);
else
{
lock = true;
return;
}
// important is that the part above never
// cause the thread to sleep.
// It is "burning" the time slice of this thread.
// Hopefully for good.
// some implementations fallback to OS lock mechanism
// after a few tries
if(lock) return beginLock(lock);
else
{
lock = true;
return;
}
}
如果你的实现不小心,可能会陷入活锁(livelock)的状态,导致所有的CPU时间都花费在锁机制上。
另请参见:
https://preshing.com/20120226/roll-your-own-lightweight-mutex/
我的自旋锁实现是否正确和最优?
总结:
死锁(Deadlock):所有进程都没有进展,什么都不做(睡眠、等待等)。CPU使用率很低;
活锁(Livelock):所有进程都没有进展,但CPU时间都花费在锁机制上,而不是计算上;
饥饿(Starvation):某个进程永远没有机会运行,由于纯粹的运气不好或者某些属性(例如低优先级);
自旋锁(Spinlock):一种避免等待锁释放成本的技术。
死锁 死锁是指一个任务无限期地等待永远不会满足的条件 - 任务声称对共享资源拥有独占控制权 - 任务在等待其他资源被释放的同时持有资源 - 任务不能被强制放弃资源 - 存在循环等待条件
活锁 当两个或多个任务依赖并使用同一资源时,可能会出现活锁条件,导致这些任务无休止地运行,从而阻塞所有优先级较低的任务运行(这些较低优先级的任务经历饥饿状态)。
也许这两个例子可以帮助你理解死锁和活锁之间的区别:
一个Java死锁的示例:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class DeadlockSample {
private static final Lock lock1 = new ReentrantLock(true);
private static final Lock lock2 = new ReentrantLock(true);
public static void main(String[] args) {
Thread threadA = new Thread(DeadlockSample::doA,"Thread A");
Thread threadB = new Thread(DeadlockSample::doB,"Thread B");
threadA.start();
threadB.start();
}
public static void doA() {
System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
lock1.lock();
System.out.println(Thread.currentThread().getName() + " : holds lock 1");
try {
System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
lock2.lock();
System.out.println(Thread.currentThread().getName() + " : holds lock 2");
try {
System.out.println(Thread.currentThread().getName() + " : critical section of doA()");
} finally {
lock2.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
}
} finally {
lock1.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
}
}
public static void doB() {
System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
lock2.lock();
System.out.println(Thread.currentThread().getName() + " : holds lock 2");
try {
System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
lock1.lock();
System.out.println(Thread.currentThread().getName() + " : holds lock 1");
try {
System.out.println(Thread.currentThread().getName() + " : critical section of doB()");
} finally {
lock1.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
}
} finally {
lock2.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
}
}
}
示例输出:
Thread A : waits for lock 1
Thread B : waits for lock 2
Thread A : holds lock 1
Thread B : holds lock 2
Thread B : waits for lock 1
Thread A : waits for lock 2
Java-一个活锁的示例:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LivelockSample {
private static final Lock lock1 = new ReentrantLock(true);
private static final Lock lock2 = new ReentrantLock(true);
public static void main(String[] args) {
Thread threadA = new Thread(LivelockSample::doA, "Thread A");
Thread threadB = new Thread(LivelockSample::doB, "Thread B");
threadA.start();
threadB.start();
}
public static void doA() {
try {
while (!lock1.tryLock()) {
System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
Thread.sleep(100);
}
System.out.println(Thread.currentThread().getName() + " : holds lock 1");
try {
while (!lock2.tryLock()) {
System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
Thread.sleep(100);
}
System.out.println(Thread.currentThread().getName() + " : holds lock 2");
try {
System.out.println(Thread.currentThread().getName() + " : critical section of doA()");
} finally {
lock2.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
}
} finally {
lock1.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
}
} catch (InterruptedException e) {
// can be ignored here for this sample
}
}
public static void doB() {
try {
while (!lock2.tryLock()) {
System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
Thread.sleep(100);
}
System.out.println(Thread.currentThread().getName() + " : holds lock 2");
try {
while (!lock1.tryLock()) {
System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
Thread.sleep(100);
}
System.out.println(Thread.currentThread().getName() + " : holds lock 1");
try {
System.out.println(Thread.currentThread().getName() + " : critical section of doB()");
} finally {
lock1.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
}
} finally {
lock2.unlock();
System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
}
} catch (InterruptedException e) {
// can be ignored here for this sample
}
}
}
示例输出:
Thread B : holds lock 2
Thread A : holds lock 1
Thread A : waits for lock 2
Thread B : waits for lock 1
Thread B : waits for lock 1
Thread A : waits for lock 2
Thread A : waits for lock 2
Thread B : waits for lock 1
Thread B : waits for lock 1
Thread A : waits for lock 2
Thread A : waits for lock 2
Thread B : waits for lock 1
...
这两个例子强制线程按不同的顺序获取锁定。当死锁等待另一个锁时,活锁并没有真正等待 - 它拼命地尝试获取锁定,但却没有获取的机会。每次尝试都会消耗CPU周期。
假设您有线程A和线程B。它们都在相同的对象上进行了synchronised,并且在这个块中有一个全局变量它们都在更新;
static boolean commonVar = false;
Object lock = new Object;
...
void threadAMethod(){
...
while(commonVar == false){
synchornized(lock){
...
commonVar = true
}
}
}
void threadBMethod(){
...
while(commonVar == true){
synchornized(lock){
...
commonVar = false
}
}
}
while
循环并持有锁时,它会执行必要的操作并将commonVar
设置为true
。然后,线程B进入,进入while
循环,并且由于commonVar
现在是true
,因此可以持有锁。它这样做,执行synchronized
块,并将commonVar
重新设置为false
。现在,线程A再次获得它的新CPU窗口,它已经准备退出while
循环,但是线程B刚刚将它重新设置为false
,所以循环重复了。线程做了些事情(因此它们不被传统意义上的阻塞),但基本上没有任何作用。synchronized
块完成执行后更喜欢另一个线程。大多数情况下,我认为这是一个难以达到的期望,并且取决于许多在幕后发生的事情。我只是想分享一些知识。
死锁是指一个线程/进程集合被死锁了,如果集合中的每个线程/进程都在等待只有集合中的另一个进程才能引发的事件。
重要的是,在同一集合中还有另一个进程。这意味着另一个进程也被阻塞了,没有人可以继续进行。
当进程被授予对资源的独占访问权时,会发生死锁。
要满足以下四个条件才能发生死锁。
如果我们发现这些条件,则可以说可能会出现死锁的情况。
活锁
每个线程/进程一遍又一遍地重复相同的状态,但不会进展。这类似于死锁,因为进程无法进入关键部分。但是在死锁中,进程正在等待而不执行任何操作,但在活锁中,进程正在尝试继续执行,但是进程一遍又一遍地重复相同的状态。
(在死锁计算中,没有可能成功的执行顺序。但在活锁计算中,有成功的计算,但存在一种或多种执行顺序,在其中没有进程进入其关键部分。)
与死锁和活锁的不同之处
当发生死锁时,不会发生任何执行。但在活锁中,一些执行将发生,但这些执行不足以进入关键部分。