这是我参与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方法修改为实际执行内容的代码块。通过将线程信息首先加入到队列中,保证了执行代码块的内容顺序是根据队列中的线程顺序,从而在根本上避免了出现线程饥饿的问题。
后记
- 千古兴亡多少事?悠悠。不尽长江滚滚流。