活锁是一种递归情况,其中两个或更多线程将继续重复特定的代码逻辑。预期的逻辑通常是给其他线程机会以支持“此”线程。
当两个人在狭窄的走廊里相遇时,发生了现实生活中的活锁例子,每个人都试图通过移动到一边让对方经过而礼貌,但最终却没有任何进展就左右摇摆,因为他们都反复移动在同一时间相同的方式。
如果系统中有两个任务,它们总是因对方的行为而改变自己的状态,那么就出现了活锁。最终结果是它们陷入了状态变更的循环而无法继续向下执行。例如,有两个任务:任务1 和任务2,它们都需要用到两个资源:资源1 和资源2。假设任务1对资源1 加了一个锁,而任务2 对资源2 加了一个锁。当它们无法访问所需的资源时,就会释放自己的资源并且重新开始循环。这种情况可以无限地持续下去,所以这两个任务都不会结束自己的执行过程。
一个线程通常会响应另一个线程的操作而行动。如果另一个线程的动作也是对另一个线程的动作的响应,则可能会导致活锁。与死锁一样,活锁的线程无法取得进一步的进展。但是,线程没有被阻塞-它们只是忙于彼此响应而无法继续工作。
例如,考虑以下情况:两个线程希望通过一个Worker对象访问共享的公共资源,但是当他们看到另一个Worker(在另一个线程上调用)也处于“活动”状态时,它们试图将资源移交给另一个Worker并等待完成它。如果最初我们让两个工人都活跃起来,他们将遭受活锁。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Livelock {
public static void main(String[] args) {
Livelock livelock = new Livelock();
new Thread(livelock::action1).start();
new Thread(livelock::action2).start();
}
private Lock lock1 = new ReentrantLock(true);
private Lock lock2 = new ReentrantLock(true);
public void action1() {
try {
while (true) {
lock1.tryLock(50, TimeUnit.MILLISECONDS);
System.out.println("LOCK 1 ACQUIRED");
TimeUnit.MILLISECONDS.sleep(1000);
if (lock2.tryLock()) {
System.out.println("LOCK 2 ACQUIRED");
} else {
System.out.println("CANNOT ACQUIRE LOCK 2, RELEASING LOCK 1");
lock1.unlock();
continue;
}
System.out.println("ACTION 1");
break;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock1.unlock();
lock2.unlock();
}
}
public void action2() {
try {
while (true) {
lock2.tryLock(50, TimeUnit.MILLISECONDS);
System.out.println("LOCK 2 ACQUIRED");
TimeUnit.MILLISECONDS.sleep(50);
if (lock1.tryLock()) {
System.out.println("LOCK 1 ACQUIRED");
} else {
System.out.println("CANNOT ACQUIRE LOCK 1, RELEASING LOCK 2");
lock2.unlock();
continue;
}
System.out.println("ACTION 2");
break;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock1.unlock();
lock2.unlock();
}
}
}
package com.example.thread.livelock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class BankAccount {
double balance;
int id;
Lock lock = new ReentrantLock();
BankAccount(int id, double balance) {
this.id = id;
this.balance = balance;
}
boolean withdraw(double amount) {
if (this.lock.tryLock()) {
// Wait to simulate io like database access ...
try {
Thread.sleep(10l);
} catch (InterruptedException e) {
}
balance -= amount;
return true;
}
return false;
}
boolean deposit(double amount) {
if (this.lock.tryLock()) {
// Wait to simulate io like database access ...
try {
Thread.sleep(10l);
} catch (InterruptedException e) {
}
balance += amount;
return true;
}
return false;
}
public boolean tryTransfer(BankAccount destinationAccount, double amount) {
if (this.withdraw(amount)) {
if (destinationAccount.deposit(amount)) {
return true;
} else {
// destination account busy, refund source account.
this.deposit(amount);
}
}
return false;
}
public static void main(String[] args) {
final BankAccount fooAccount = new BankAccount(1, 500d);
final BankAccount barAccount = new BankAccount(2, 500d);
new Thread(new Transaction(fooAccount, barAccount, 10d), "transaction-1").start();
new Thread(new Transaction(barAccount, fooAccount, 10d), "transaction-2").start();
}
}
class Transaction implements Runnable {
private BankAccount sourceAccount, destinationAccount;
private double amount;
Transaction(BankAccount sourceAccount, BankAccount destinationAccount, double amount) {
this.sourceAccount = sourceAccount;
this.destinationAccount = destinationAccount;
this.amount = amount;
}
public void run() {
while (!sourceAccount.tryTransfer(destinationAccount, amount))
continue;
System.out.printf("%s completed ", Thread.currentThread().getName());
}
}
避免活锁
在上面的示例中,我们可以通过顺序处理公共资源而不是同时在不同线程中处理此问题。
就像死锁一样,也没有通用的准则来避免活锁,但是在更改其他线程也正在使用的公共对象的状态的情况下,例如在上述情况下,我们必须要小心。Worker对象。