Java多线程饥饿和活锁都是什么鬼?

535 阅读4分钟

「这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战

多线程饥饿与Java多线程活锁

先看下多线程活锁和多线程死锁和多线程饥饿的区别

活锁:
任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试、失败、尝试、失败。在这期间线程状态会不停的改变

活锁与死锁的区别:
死锁会阻塞,一直等待对方释放资源,一直处在阻塞状态;活锁会不停的改变线程状态尝试获得资源。活锁有可能自行解开,死锁则不行

饥饿:
一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行的状态。一直有线程级别高的暂用资源,线程低的一直处在饥饿状态。
比如ReentrantLock显示锁里提供的不公平锁机制,不公平锁能够提高吞吐量但不可避免的会造成某些线程的饥饿

死锁与饥饿的区别:
线程处于饥饿是因为不断有优先级高的线程占用资源,当不再有高优先级的线程争抢资源时,饥饿状态将会自动解除。

产生饥饿的原因:

  • 高优先级线程抢占资源
  • 线程在等待一个本身也处于永久等待完成的对象
  • 线程被永久阻塞在一个等待进入同步快的状态,因为其他线程总是能在它之前持续地对该同步块进行访问

线程饥饿

线程饥饿是指线程一直无法获得所需要的资源导致任务一直无法执行的一种活性故障。

我们知道多线程执行中有线程优先级这个东西,优先级高的线程能够插队并优先执行,这样如果优先级高的线程一直抢占优先级低线程的资源,导致低优先级线程无法得到执行,这就是饥饿。当然还有一种饥饿的情况,一个线程一直占着一个资源不放而导致其他线程得不到执行,与死锁不同的是饥饿在以后一段时间内还是能够得到执行的,如那个占用资源的线程结束了并释放了资源。

线程饥饿的一个典型例子就是在高争用环境中使用非公平模式的读写锁.读写锁默认情况下采用非公平调度模式,如果这些线程对锁的争用程度比较高,有可能会出现读锁总是抢先执行,而写锁始终无法获得的情况,导致一直无法更新数据.非公平锁可以支持更高的吞吐率,也可能导致某些线程始终无法获得资源锁。

在高争用环境中,由于线程优先级设置不当,可能会导致优先级低的线程一直无法获得CPU执行权,出现了线程饥饿的情况。

package com.wkcto.threadactivity.starvation;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 线程饥饿
 * 由于线程所需要的资源一直无法获得导致线程一直处于等待状态
 */
public class Test {
    public static void main(String[] args) {
        //创建只有一个线程的线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        //向线程池中添加取数据任务与添加数据的任务
        executorService.submit(new TakeDataTask());
        executorService.submit(new AddDataTask());
        executorService.shutdown();
    }

    //创建一个阻塞队列
    private static final BlockingQueue<Integer> QUEUE = new ArrayBlockingQueue<>(10);
    //定义一个向阻塞队列中添加数据的任务
    private static class AddDataTask implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getId() +  " 编号的线程执行添加数据的任务");
            try {
                QUEUE.put(123);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //定义从队列中取数据的任务
    private static class  TakeDataTask implements Runnable{
        @Override
        public void run() {
            System.out.println( Thread.currentThread().getId()  + " 编号的线程执行取数据的任务");
            try {
                Integer data = QUEUE.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

活锁

活锁具有两个特点:

  • 第一个是线程没有阻塞, 始终在运行中,所以叫活锁, 线程是活的, 运行中的.
  • 第二个是程序却得不到进展, 因为线程始终重复同样的无效事情.