Java并发编程之饥渴与公平
如果一个线程没有被授予CPU时间,因为其他线程攫取了它的全部CPU时间,这被称为“饥饿”。这个线程“饿死了”,因为其他线程被允许占用CPU时间。解决饥饿问题的方法称为“公平”——即所有线程都被公平地给予执行机会。
产生饥渴的原因
高优先级的线程从低优先级的线程中吞噬所有CPU时间
您可以分别设置每个线程的线程优先级。优先级越高,线程被授予的CPU时间就越多。可以将线程的优先级设置为1到10。具体如何解释取决于应用程序所运行的操作系统。对于大多数应用程序,最好保持优先级不变。
线程被不确定地阻塞以等待进入一个同步块,因为其他线程总是被允许在它之前访问它。
Java的同步代码块可能是导致资源短缺的另一个原因。Java的同步代码块不能保证等待进入同步块的线程被允许进入的顺序。这意味着理论上存在这样的风险,即一个线程在尝试进入块时永远处于阻塞状态,因为其他线程总是在它之前被授予访问权。这个问题被称为“饥饿”,即一个线程“饿死”,因为其他线程被允许占用CPU时间
等待对象的线程(调用该对象上的wait())会无限期地等待,因为其他线程会不断地被唤醒
如果调用notify()对象的多个线程调用了wait(),则notify()方法不能保证会唤醒哪个线程。它可以是任何等待的线程。因此,存在等待某个对象的线程永远不会被唤醒的风险,因为总是有其他等待的线程被唤醒。
使用同步块实现公平竞争
public class MyClass{
//声明同步代码块
public synchronized void doSynchronized(){
//dosomething
}
}
如果有多个线程调用doSynchronized()方法,其中一些线程将被阻塞,直到第一个被授予访问权限的线程离开该方法。如果阻塞了多个线程等待访问,则无法保证下一个线程被授予访问权限。
使用Lock锁实现公平竞争
public class MyClass{
Lock lock = new Lock();
public void doSynchronized() throws InterruptedException{
this.lock.lock();
//需要锁的部分
this.lock.unlock();
}
}
public class Lock{
private boolean isLock = false;
private Thread lockingThread = null;
public synchronized void lock() throws InterruptedException{
while(isLock){
wait();
}
this.lock = true;
lockingThread = Thread.currentThread();
}
public synchronized void unlock() throws InterruptedException{
if(this.lockingThread != Thread.currentThread()){
throw new IllegalMonitorStateException(
"Calling thread has not locked this lock");
}
this.isLock = false;
this.lockingThread = null;
notify();
}
}
如果多个线程同时调用Lock(),那么现在尝试访问Lock()方法的线程会被阻塞。其次,如果锁被锁定,那么线程将在lock()方法的while(isLocked)循环中的wait()调用中被阻塞。记住,调用wait()的线程释放锁实例上的同步锁,因此等待进入lock()的线程现在可以这样做。结果是,多个线程可能最终在lock()中调用了wait().
使用队列达到公平
public class MyQueueObject{
private boolean isNotified = false;
public synchronized void doWait() throws InterruptedException{
while(isNotified){
this.wait();
}
this.isNotified = false;
}
public synchronized void doNotify(){
this.isNotified = true;
this.notify();
}
public boolean equals(Object o){
return this == o;
}
}
public class MyLock{
private boolean isLock = false;
private Thread lockingThread = null;
private List<MyQueueObject> waitingThreads = new ArrayList<>();
public void lock() throws InterruptedException{
MyQueueObject myQueueObject = new MyQueueObject ();
boolean isLockedForThread = true;
synchronized(this){
waitingThreads .add(myQueueObject);
}
while(isLockedForThread ){
synchronized(this){
isLockedForThread = isLock || waitingThreads.get(0) != myQueueObject;
if(!isLockedForThread ){
isLock = true;
waitingThreads.remove(myQueueObject);
lockingThread = Thread.currentThread();
return ;
}
}
}
try{
queueObject.doWait();
}catch(InterruptedException e){
synchronized(this) { waitingThreads.remove(myQueueObject); }
throw e;
}
}
}
public synchronized void unlock(){
if(this.lockingThread != Thread.currentThread()){
throw new IllegalMonitorStateException(
"Calling thread has not locked this lock");
}
isLock = false;
lockingThread = null;
if(waitingThreads.size() > 0){
waitingThreads.get(0).doNotify();
}
}
}
MyLock创建一个MyQueueObject的新实例,并为每个调用lock()的线程排队。调用unlock()的线程将获取队列中最顶端的MyQueueObject并对其调用doNotify(),以唤醒等待该对象的线程。这样,一次只唤醒一个等待的线程,而不是所有等待的线程。这部分决定了MyLock的公平性。
还要注意,MyQueueObject实际上是一个信号量。doWait()和doNotify()方法将信号内部存储在MyQueueObject中。这样做是为了避免一个线程在调用myQueueObject.doWait()之前被另一个调用unlock()从而调用myQueueObject.doNotify()的线程抢占而导致的信号丢失。myQueueObject.doWait()调用被放在synchronized(this)块之外,以避免嵌套的监视器锁定,因此,当lock()方法中的synchronized(this)块中没有线程执行时,另一个线程实际上可以调用unlock()。