内卷老员工之线程饥饿与公平

670 阅读2分钟

这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战

线程饥饿与公平

饥饿与公平的概念

  • 如果一个线程因为其他线程将cpu时间全部占用而无法被授予cpu时间,则成为“饥饿”。饥饿的解决方案为“公平”--为所有线程提供平等的执行机会。

java饥饿的原因

  • 以下三个原因经常会导致java线程的饥饿
    • 高优先级线程从低优先级线程中吞噬所有时间。
    • 线程被无线阻塞进入同步代码块,因为其他线程总是在它之前被允许访问。
    • 线程无限等待对象(调用了wait()方法),因为其他线程会不停被唤醒而不是它。

高优先级线程从低优先级线程中吞噬所有cpu时间

  • 线程可以单独设置优先级,优先级越高获得的cpu时间越多。多数情况下应保持优先级不变。

线程无限阻塞等待进入同步代码块

  • java的同步代码块不会保证等待进入同步块的线程被顺序执行。这就意味着理论上的风险,某个线程会无限进入循环等待的状态。

等待已调用wait()的对象陷入循环等待

  • 如果在多个线程在同个对象上调用了wait()方法,则notify()唤醒的不一定是哪一个线程,则可能存在某个线程陷入循环等待的状态。

在java中实现公平性

同步块中存在的问题

    public synchronized void method(){ 
        //do something
      } 
  • 对于使用synchronized方法修饰的方法,当多个线程同时调用时,其他线程会在方法外等待。由于不能保证是哪一个线程会先执行锁定的代码,因此会导致线程饥饿问题。

使用lock代替同步块

public class Lock{
    public synchronized void lock(){
        // do something
    } 
}
Lock lock = new Lock();
public void method(){
    lock.lock();
    // do something
    lock.unlock();
}
  • 当使用自定义lock代替synchronized时,代码会有方法的阻塞变更为lock锁定位置的阻塞。但同样的原理,采用了synchronized也会存在线程饥饿的问题。

使用FairLock实现公平

public class Fairlock{
    private List<QueueObject> waitingThreads = 
            new ArrayList<QueueObject>(); 
    public void lock(){
        waitingThreads.add(//"线程信息");
        synchronized(this){
            // do something
        }
    }
}
  • 通过自定义fairlock类,将synchronized的修饰对象也从lock方法修改为实际执行内容的代码块。通过将线程信息首先加入到队列中,保证了执行代码块的内容顺序是根据队列中的线程顺序,从而在根本上避免了出现线程饥饿的问题。

后记

  • 千古兴亡多少事?悠悠。不尽长江滚滚流。