什么是活锁和饥饿?

2019-09-26  

活锁

任务没有被阻塞,由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程。 处于活锁的实体是在不断的改变状态,活锁有可能自行解开。

死锁是大家都拿不到资源都占用着对方的资源,而活锁是拿到资源却又相互释放不执行。

 

解决活锁的一个简单办法就是在下一次尝试获取资源之前,随机休眠一小段时间。

 

看一下,我们之前的一个例子,如果最后不进行随机休眠,就会产生活锁,现象就是很长一段时间,两个线程都在不断尝试获取和释放锁。

package constxiong.concurrency.a023;

import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 测试 占有部分资源的线程进一步申请其他资源时,如果申请不到,主动释放它占有的资源,破坏 "不可抢占" 条件
 * @author ConstXiong
 * @date 2019-09-24 14:50:51
 */
public class TestBreakLockOccupation {
	
	private static Random r = new Random(); 

	private static Lock lock1 = new ReentrantLock();
	
	private static Lock lock2 = new ReentrantLock();
	
	public static void main(String[] args) {
		new Thread(() -> {
			//标识任务是否完成
			boolean taskComplete = false;
			while (!taskComplete) {
				lock1.lock();
				System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock1 成功");
				try {
					//随机休眠,帮助造成死锁环境
					try {
						Thread.sleep(r.nextInt(30));
					} catch (Exception e) {
						e.printStackTrace();
					}
					
					//线程 0 尝试获取 lock2
					if (lock2.tryLock()) {
						System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock2 成功");
						try {
							taskComplete = true;
						} finally {
							lock2.unlock();
						}
					} else {
						System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock2 失败");
					}
				} finally {
					lock1.unlock();
				}
				
				//随机休眠,避免出现活锁
				try {
					Thread.sleep(r.nextInt(10));
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}).start();
		
		new Thread(() -> {
			//标识任务是否完成
			boolean taskComplete = false;
			while (!taskComplete) {
				lock2.lock();
				System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock2 成功");
				try {
					//随机休眠,帮助造成死锁环境
					try {
						Thread.sleep(r.nextInt(30));
					} catch (Exception e) {
						e.printStackTrace();
					}
					
					//线程2 尝试获取锁 lock1
					if (lock1.tryLock()) {
						System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock1 成功");
						try {
							taskComplete = true;
						} finally {
							lock1.unlock();
						}
					} else {
						System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock1 失败");
					}
				} finally {
					lock2.unlock();
				}
				
				//随机休眠,避免出现活锁
				try {
					Thread.sleep(r.nextInt(10));
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}).start();
	}
	
}

 

饥饿

一个线程因为 CPU 时间全部被其他线程抢占而得不到 CPU 运行时间,导致线程无法执行。

产生饥饿的原因:

  • 优先级线程吞噬所有的低优先级线程的 CPU 时间
  • 其他线程总是能在它之前持续地对该同步块进行访问,线程被永久堵塞在一个等待进入同步块
  • 其他线程总是抢先被持续地获得唤醒,线程一直在等待被唤醒
package constxiong.concurrency.a024;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * 测试线程饥饿
 * @author ConstXiong
 */
public class TestThreadHungry {

	private static ExecutorService es = Executors.newSingleThreadExecutor();
	
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		Future<String> future1 = es.submit(new Callable<String>() {
			@Override
			public String call() throws Exception {
				System.out.println("提交任务1");
				Future<String> future2 = es.submit(new Callable<String>() {
					@Override
					public String call() throws Exception {
						System.out.println("提交任务2");
						return "任务 2 结果";
					}
				});
				return future2.get();
			}
		});
		System.out.println("获取到" + future1.get());
	}
	
}

 

打印结果如下,线程池卡死。线程池只能容纳 1 个任务,任务 1 提交任务 2,任务 2 永远得不到执行。

提交任务1

 

ConstXiong 备案号:苏ICP备16009629号-3