java线程知识点拾遗(排队CAS)

在java线程知识点拾遗(CAS)这篇文章中简单的介绍了cas的概念,但是普通的cas是有一个非公平的问题,多个线程需要共享资源的时候可能造成忙等待问题,一言以蔽之就是可能后请求共享资源的线程可能比先请求资源的线程优先获取到资源。
用个例子来说明cas的非公平性,代码如下:

public class UnFairCasLock implements Runnable {
	private AtomicBoolean V = new AtomicBoolean(true);
	private boolean A = true;
	private boolean B = false;

	@Override
	public void run() {
		lock();
		PrintUtils.println("服务员为"+Thread.currentThread().getName()+"打菜中");
		ThreadUtils.sleep(2000);
		unlock();
	}

	//模拟自旋
	private void lock() {
		while (!V.compareAndSet(A, B)) {
			// 加一个sleep操作,防止while循环过快执行
			ThreadUtils.sleep(1000);

		}
	}

	private void unlock() {
		V.compareAndSet(B, A);
	}

然后初始化多个线程来访问,用来模拟食堂服务员打菜的逻辑:

	public static void main(String args[]) {
		UnFairCasLock unFairCasLock = new UnFairCasLock();
		char name='A';
		for(int i=1;i<10;i++) {
			Thread thread = new Thread(unFairCasLock);
		    thread.setName(""+ name);
			name++;
			thread.start();
		}//end for		
	}

我们用for循环初始化了几个线程,并且为线程命名为A-I,运行结果如下:

服务员为A打菜中
服务员为C打菜中
服务员为B打菜中
服务员为G打菜中
服务员为H打菜中
服务员为F打菜中
服务员为E打菜中
服务员为D打菜中
服务员为I打菜中

可以打菜的顺序并不是按到A-I的顺序来完成打菜的,并没有什么公平性可言!所以怎么处理这种问题呢?排队自旋锁的出现解决了这个问题!让每个打菜的童鞋排队取号,每个学生请求打菜的时候先拿一个排队号(waitingNum),同时排队自旋锁拥有一个服务号,表示当前正在享受打菜的童鞋,拿到waitingNum的童鞋不断轮训锁当前的服务号是否是自己的排位号,如果是,说明该轮到服务员给自己打菜了,要不然继续眼巴巴的轮询,有点类似于去银行办事取号的逻辑。说了这么多,还是用代码说话吧:

//公平自旋锁
public class FairCasLock implements Runnable{
	//服务号
	private AtomicInteger service = new AtomicInteger(1);	
	//排队号
	private AtomicInteger waiting = new AtomicInteger(1);
	
	/**
	 *  请求 给自己打菜,拿到号码,多个线程同事调用lock方法时,waitingNum是递增的
	 * @return
	 */
	public int lock() {
		//获取排队号码,先获取 后++
		int waitingNum = waiting.getAndIncrement();
		//如果当前服务号不是自己,那么继续轮询
		while(service.get()!= waitingNum) {		
			ThreadUtils.sleep(1000);
		}
		return waitingNum;
	}
	
	/**
	 * 
	 * @param serviceNum正在服务的同学
	 */
	public void unLock(int serviceNum) {
		//下一个接受服务的服务号
		int waitingNum= service.get()+1;
		service.compareAndSet(serviceNum, waitingNum);
	}
	}

注意上面的lock方法,先获取一个排队号,如果当前服务的服务号不是自己的排队号,说明还轮不到自己打菜,继续乖乖等待。注意在获取服务号之后,waiting这个变量是自增的。所以当多个线程调用lock的时候会保持waingNum的自增。还有就是unLock方法多了一个serviceNum的参数,这个也很好理解,释放的当然是之前正在打菜的童鞋的排队号(此时是服务号)。
同样的搞一个for循环,创造几个线程来执行之:

public static void main(String[] args) {
		FairCasLock fariCasLock = new FairCasLock();
		char c='A';
		for(int i=1;i<10;i++) {
			Thread thread = new Thread(fariCasLock);
			thread.setName(""+ c);
			c++;
			thread.start();
		}//end for		

	}

运行结果如下:

服务员为第1号学生A打菜中
服务员为第2号学生B打菜中
服务员为第3号学生C打菜中
服务员为第4号学生E打菜中
服务员为第5号学生D打菜中
服务员为第6号学生F打菜中
服务员为第7号学生G打菜中
服务员为第8号学生H打菜中
服务员为第9号学生I打菜中

完美的实现了公平,当然没有完美这一说,比如一个排队都靠后的童鞋都快饿晕了,应该让其优先级提高优先打菜!再比如火车到站排队验票的时候,有个人来的晚但是火车马上要开了,也是需要插队提前检票的?但是本博文排队自旋锁暂时没有这个提高优先级的功能,排队自旋锁同样具有其缺点,下一篇博文描述之

你可能感兴趣的:(排队CAS,thread)