Answers:
取自http://en.wikipedia.org/wiki/Deadlock:
在并发计算中,死锁是一种状态,其中一组动作的每个成员都在等待某个其他成员释放锁
阿活锁类似于死锁,不同之处在于所涉及的活锁的过程的状态不断地关于彼此,无进展而改变。Livelock是资源匮乏的特例;一般定义仅指出特定过程没有进展。
当两个人在狭窄的走廊里相遇时,发生了现实生活中的活锁例子,每个人都试图通过移动到一边让对方经过而礼貌,但是最终却没有任何进展就左右摇摆,因为他们都反复移动在同一时间相同的方式。
对于某些检测死锁并从死锁中恢复的算法,活锁是一种风险。如果有多个进程采取措施,则死锁检测算法可以重复触发。通过确保只有一个过程(随机选择或优先选择)可以采取措施,可以避免这种情况。
一个线程通常会响应另一个线程的操作而行动。如果另一个线程的动作也是对另一个线程的动作的响应,则可能导致活动锁。
与死锁一样,活锁的线程无法取得进一步的进展。但是,线程并未被阻塞 -它们只是太忙于彼此响应而无法恢复工作。这相当于两个人试图在走廊中互相经过:阿方斯(Alphonse)向左移动以让加斯顿(Gaston)通过,而格斯顿(Gaston)向右移动以让Alphonse通过。看到他们仍然互相阻挡,阿方斯(Alphonse)向右移动,而加斯顿(Gaston)向左移动。他们仍然互相阻碍,依此类推...
活锁和死锁之间的主要区别在于,线程不会被阻塞,而是将尝试不断进行响应。
在此图中,两个圆圈(线程或进程)将尝试通过左右移动来给另一个空间。但是他们不能再前进了。
这里的所有内容和示例均来自
操作系统:内部结构和设计原则
William Stallings8º
版
死锁:一种情况,其中两个或多个进程无法进行,因为每个进程都在等待另一个进程在做某事。
例如,考虑两个进程P1和P2,以及两个资源R1和R2。假设每个进程都需要访问两个资源才能执行其部分功能。然后可能出现以下情况:OS将R1分配给P2,将R2分配给P1。每个进程都在等待两种资源之一。在获得另一资源并执行需要这两种资源的功能之前,它们都不会释放它已经拥有的资源。这两个过程陷入僵局
活锁(Livelock):两个或多个进程在不做任何有用工作的情况下,不断响应其他进程的更改而更改其状态的情况:
饥饿:调度程序无限期地忽略可运行进程的情况;尽管它可以继续进行,但从未选择。
假设三个进程(P1,P2,P3)每个都需要定期访问资源R。请考虑以下情况:P1拥有该资源,并且P2和P3都被延迟,等待该资源。当P1退出其关键部分时,应允许P2或P3访问R。假定OS授予对P3的访问权限,并且P1在P3完成其关键部分之前再次需要访问。如果OS在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;
[...]考虑以下事件顺序:
此序列可以无限期地扩展,任何过程都不能进入其关键部分。严格来说,这不是死锁,因为这两个过程的相对速度的任何改变都将打破此循环并允许一个进入关键部分。这种情况称为“ 活锁”。回想一下,当一组进程希望进入其关键部分但没有成功的进程时,就会发生死锁。使用livelock时,可能会有成功的执行序列,但是也可能描述一个或多个执行序列,其中没有进程进入其关键部分。
不再满足于此书。
那自旋锁呢?
自旋锁是一种避免操作系统锁定机制成本的技术。通常,您会这样做:
try
{
lock = beginLock();
doSomething();
}
finally
{
endLock();
}
当beginLock()
成本远高于成本时,问题开始出现doSomething()
。用非常夸张的术语,想象一下当beginLock
花费1秒但doSomething
仅花费1毫秒时会发生什么。
在这种情况下,如果您等待1毫秒,则可以避免受到1秒钟的阻碍。
为什么beginLock
要花这么多钱?如果没有锁是免费的(请参阅https://stackoverflow.com/a/49712993/5397116),但是如果没有锁,则操作系统将“冻结”您的线程,请设置一种机制来唤醒您释放锁后,以后再唤醒您。
所有这些都比某些检查锁的循环要昂贵得多。这就是为什么有时最好做一个“自旋锁”的原因。
例如:
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;
}
}
如果您的实现不仔细,您可能会陷入动态锁定,将所有CPU都花在锁定机制上。
另请参阅:
https://preshing.com/20120226/roll-your-own-lightweight-mutex/
我的自旋锁实现正确且最佳吗?
总结:
死锁:无人进步,无所事事(睡觉,等待等)的情况。CPU使用率会很低;
Livelock:这种情况下,没有任何进展,但是CPU花费在锁定机制上而不是计算上;
饥饿:一个女主角永远没有机会奔跑的情况;纯粹由于运气不好或由于其某些性质(例如,低优先级);
自旋锁(Spinlock):避免等待释放锁的成本的技术。
DEADLOCK 死锁是一种情况,其中任务无限期地等待着永远无法满足的条件-任务要求对共享资源的独占控制权-任务在等待其他资源释放时保留资源-不能强迫任务重新分配资源-循环等待条件存在
LIVELOCK 当两个或两个以上任务依赖并使用某些资源并导致循环依赖条件时,这些任务将永远运行,从而导致所有优先级较低的任务无法运行(这些优先级较低的任务遇到一种称为饥饿的条件),则可能会出现Livelock条件
也许这两个示例向您说明了死锁和活动锁之间的区别:
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
}
}
}
因此,当线程A进入while
循环并持有锁时,它将执行它必须做的并将其设置commonVar
为true
。然后线程B进来,在进入while
循环,因为commonVar
是true
现在,它是能够持有锁。这样做,执行该synchronised
块,并将其设置commonVar
回false
。现在,线程A再次得到它的新的CPU窗口,它是要退出while
循环,但线程B刚刚设置回false
,如此循环重复了一遍。线程可以执行某些操作(因此在传统意义上不会被阻塞),但几乎没有任何作用。
可能还需要提及的是,活锁不一定必须出现在此处。我假设一旦synchronised
块完成执行,调度程序就会偏向另一个线程。在大多数情况下,我认为这是一个很难达到的期望,它取决于引擎盖下发生的许多事情。