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()。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值