java线程知识点拾遗(CAS)

CAS:简单的来说就是比较交换!那么比较的是什么?交换的又是什么呢?
CAS有三个操作数,V,A,B。要比较的就是V和A,当V和A相等的时候,就将V的值更新为B.
感觉就像“天王盖地虎”对“小鸡炖蘑菇”一样,暗号对上了(V==A)就可以进行下一步的操作(更新)了
上面这段描述可以简单的伪代码表示为:

if(V==A){
   V=B;
}

当然这并没有体现出自旋的特性,将上面的这段伪代码用一个方法表示就是V.compareAndSet(A, B),用代码来解释更容易:

1、首先定义一个类,名为CAS,该类实现了Runnable,为CAS类提供V、A、B三个变量

	private AtomicBoolean V = new AtomicBoolean(true);
	private boolean A = true;
	private boolean B = false;

2、为CAS类设计两个方法在lock方法模拟自旋,unlock方法将V值复位:

	//模拟自旋
	private void lock() {
		while (!V.compareAndSet(A, B)) {
			// do nothing
			println(Thread.currentThread().getName() + "--lock--进入自旋转-");
			// 加一个sleep操作,防止while循环过快执行
			sleep(1000);
		}
	}

  //V值复位为false
	private void unlock() {
		V.compareAndSet(B, A);
		println(Thread.currentThread().getName() + "----unlock-V-" + V.get());
	}

	public void sleep(long time) {
		try {
			Thread.sleep(time);
		} catch (Exception e) {

		}
	}

看lock方法其实很简单,就是一个while循环不断比较!V.compareAndSet(A, B),为了防止调用速度过快,博主可以sleep了一秒钟。
3、在Runnable的run方法中这么调用:

public void run() {
		lock();
		println(Thread.currentThread().getName() + " 开始工作,此时V的值修改为==" + V.get());
		sleep(5000);
		unlock();
	}

4、开启两个Thread执行CAS:


	public static void main(String args[]) {
		CAS cas = new CAS();
		
		Thread a = new Thread(cas);
		a.setName("线程A");

		Thread b = new Thread(cas);
		b.setName("线程B");

		a.start();
		b.start();
	}

5、执行结果就是:

线程A 开始工作,此时V的值修改为==false
线程B--lock--进入自旋转-
线程B--lock--进入自旋转-
线程B--lock--进入自旋转-
线程B--lock--进入自旋转-
线程B--lock--进入自旋转-
线程B--lock--进入自旋转-
线程A----unlock-V-true
线程B 开始工作,此时V的值修改为==false
线程B----unlock-V-true

从上面的打印可以看出CAS对于两个线程来说,都没有挂起;都在各自执行自己的东西,比如A线程执行自己的打印,B线程就不断的执行cas自旋转。所以CAS的也成为无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步,所以可以用CAS来模拟实现乐观锁,为了区别,这里可以简单的写一个demo,使用sun.misc.Lock来改写我们的run方法:

  //定义一把锁
// 定义一把锁
	private Lock lock = new Lock();

	public void run() {
		// 加锁
		try {
			lock.lock();
			PrintUtils.println(Thread.currentThread().getName() + "-获取到了锁--");
		} catch (InterruptedException e) {
		}

		PrintUtils.println(Thread.currentThread().getName() + " 开始工作五秒钟。。。。。");
		ThreadUtils.sleep(5000);
	
		PrintUtils.println(Thread.currentThread().getName() + "工作完毕,释放锁-");
		lock.unlock();
	}

继续执行上面的main方法,运行效果如下(可以看出A获取锁的时候,B就阻塞住了):

线程A-获取到了锁--
线程A 开始工作五秒钟。。。。。
线程A工作完毕,释放锁-
线程B-获取到了锁--
线程B 开始工作五秒钟。。。。。
线程B工作完毕,释放锁-


对于上面的CAS的代码,如果不用lock和unlock的话,我们也可以把run方法改写成如下所示:

public void run() {
		if (V.compareAndSet(A, B)) {// 此时当前线程符合要求
			println(Thread.currentThread().getName() + " 开始工作,此时V修改为==" + V.get());
			sleep(5000);		
			V.set(A);
			println(Thread.currentThread().getName() + "unlock-V-"+V.get());
		} else {
			println(Thread.currentThread().getName() + "--lock--进入自旋转-");
			// 防止StackOverflowError
			sleep(1000);
			//invoke self,模拟CAS
			run();
		}
	}

需要注意到是else分支调用run方法自己的时候,之所以sleep(1000),是为了防止调用过快,造成StackOverflowError的错误!


当然还有一个更优雅的自旋实现方法,用AtomicReference这个类!代码如下:

	private AtomicReference<Thread> owner = new AtomicReference<Thread>();
  
	@Override
	public void run() {
		lock();
		PrintUtils.println(Thread.currentThread().getName() + " 开始工作五秒钟....");
		ThreadUtils.sleep(5000);
		unlock();
	}

	//模拟自旋
	private void lock() {
		Thread current = Thread.currentThread();

		while(!owner.compareAndSet(null, current)) {
			PrintUtils.println(current + " 开始自旋" );
			ThreadUtils.sleep(1000);
		}
	}

	private void unlock() {
		Thread current = Thread.currentThread();
		PrintUtils.println(current.getName() + "工作完毕");
		owner.compareAndSet(current, null);
	
	}


运行效果如下:

线程A 开始工作五秒钟....
Thread[线程B,5,main] 开始自旋
Thread[线程B,5,main] 开始自旋
Thread[线程B,5,main] 开始自旋
Thread[线程B,5,main] 开始自旋
Thread[线程B,5,main] 开始自旋
Thread[线程B,5,main] 开始自旋
线程A 工作完毕
线程B 开始工作五秒钟....
线程B 工作完毕

自旋转锁保证了在不阻塞的情况下对共享资源的互斥访问,自旋锁不会使线程状态发生切换,一直处于用户态,即线程一直都是active的;不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快。但是其缺乏公平性。为什么这么说呢?考虑一个现实的场景,博主高中的时候去学校食堂吃饭,在一个小窗口打饭的时候,刚开始几乎没人排队,然后大家一拥而上,跟打仗似的;但是就一个打菜的阿姨(共享资源),当阿姨跟一个人打菜的时候,后面的就得等着;但是当前一个人打完之后,后面的谁劲大,谁挤到前面就有优先打到菜的好处,根本没有什么先来后到这一说。这就不公平了。同样的当一个自旋锁被使用期间,别的需要该锁CPU就需要等待,一旦锁被释放,这些等待的CPU谁会首先获取锁呢?那就不得而知了,有可能最先请求锁的那个CPU最后获得锁。

那么怎么解决这个问题呢?排队自旋锁应运而生,就像后来博主的高中食堂开始排队打菜一样,具体什么是排队自旋锁,后面会继续说明
关于CAS,就简单的介绍到这儿,如有不当之处欢迎批评指正,共同学习和提高。博客demo代码传送门

发布了257 篇原创文章 · 获赞 484 · 访问量 144万+

猜你喜欢

转载自blog.csdn.net/chunqiuwei/article/details/99463576