上期提到过AtomicInteger ,其中一个赋值方法compareAndSet(),CAS就是原子类比较并设值方法的缩写。
CAS 全程是CompareAndSwap 比较并交换,是一条CPU原语,这个过程是原子的
CAS 简单实用
package com.cas; import java.util.concurrent.atomic.AtomicInteger; /** * @author liuxu * @date 2021/11/10 21:59 */ public class CasDemo { public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(10); System.out.println(atomicInteger.compareAndSet(10,100)+"当前值是:"+atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(10,100)+"当前值是:"+atomicInteger.get()); } }
如果和预期值不一致,会导致值修改失败
CAS底层原理可以从
atomicInteger.incrementAndGet();看起
/*this 当前对象 valueOffset 内存偏移量 unsafe unsafe类,cas关键 */ public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; } unsafe类下方法 var1 当前对内存偏移量 var2 内存偏移量 var4 增加值 public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); //相当于从主物理内存拿到当前对象物理内存上的值 var5 } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; } //这个方法在java中看不到实现,可以在java源码包中找到,sun.misc包下可以看到源码 /* *这个方法首先会将 内存偏移量得到的值var5 和 原来的值var1进行比较 如果相同,说明当前 *线程备份值和内存中的值相同(这里不明白可以看第一期)JMM模型中线程操作内存的方法,可以进行值的增加, *返回ture,在原来值上增加var4,操作成功,由于方法native修饰,此方法线程安全 *如果失败,只能重新获取内存偏移量上的值,直到设置成功。 */ public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
CAS的缺点
1.如果一个线程一直未CAS设值成功,就会while循环,可能会给CPU带来较大开销
2.会存在ABA问题,如果A线程拷贝了主内存中的值,但是还没有进行值的修改,
此时B线程将主线程中的值由A改成B,然后再改成A,此时如果A线程进行CAS操作,
会认为主线程的值并没有修改
3.可以使用的值比较单一
原子引用可以解决这个问题,首先他可以将对对象封装成原子类
package com.cas; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; /** * @author liuxu * @date 2021/11/10 22:42 */ public class AtomDemo { public static void main(String[] args) { AtomicReference<String> reference = new AtomicReference<>(); reference.set("张三"); System.out.println(reference.get()); boolean b = reference.compareAndSet("张三", "zhang"); System.out.println(b+reference.get()); boolean c = reference.compareAndSet("张三", "zhang"); System.out.println(c+reference.get()); } }
版本号引用,解决ABA问题
package com.cas; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicStampedReference; /** * @author liuxu * @date 2021/11/10 22:51 */ public class ABADemo { static AtomicReference<Integer> atomicReference = new AtomicReference<>(0);//原子引用 static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(0,1);//版本号原子引用 public static void main(String[] args) { new Thread(()->{ atomicReference.compareAndSet(0,100); atomicReference.compareAndSet(100,0); },"A").start(); new Thread(()->{ try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } boolean b = atomicReference.compareAndSet(0, 100); System.out.println(Thread.currentThread().getName()+"设置值"+b); },"B").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("以上说明ABA问题存在,版本号原子引用可以解决"); new Thread(()->{ try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } stampedReference.compareAndSet(0,100,stampedReference.getStamp(),stampedReference.getStamp()+1); stampedReference.compareAndSet(100,0,stampedReference.getStamp(),stampedReference.getStamp()+1); },"C").start(); new Thread(()->{ int stamp = stampedReference.getStamp(); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } boolean andSet = stampedReference.compareAndSet(0, 2009, stamp, stamp + 1); System.out.println(Thread.currentThread().getName()+"获取的版本号是"+stamp); System.out.println(Thread.currentThread().getName()+"实际的版本号是"+stampedReference.getStamp()); System.out.println(Thread.currentThread().getName()+"是否修改成功"+andSet); },"D").start(); } }
CountDownLatch 用于多线程间递减计数,唤醒另一线程。
一个或者多个线程功来调用 countDownLatch.await(); 如果 countDownLatch不是0就会被阻塞
直到 countDownLatch.countDown();将初始值递减到0
package com.cas; import java.util.concurrent.CountDownLatch; /** * @author liuxu * @date 2021/11/11 20:20 */ /** * CountDownLatch countDownLatch.countDown()递减计数, * 直到为0 countDownLatch.await();才会放行countDownLatch.await()所在线程 */ public class CountDownLathDemo { public static void main(String[] args) { CountDownLatch countDownLatch = new CountDownLatch(10); for (int i = 0; i <10 ; i++) { new Thread(()->{ countDownLatch.countDown(); System.out.println("第"+Thread.currentThread().getName()+"个人离开"); },i+"").start(); } try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("闭门关灯"); } }
CyclicBarrier
CyclicBarrier构造时声明最终目标值和要运行的方法,
cyclicBarrier.await();一次,cyclicBarrier从0自增1,达到目标值,lamda表达式中的方法运行
package com.cas; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; /** * @author liuxu * @date 2021/11/11 20:50 */ public class CyclicBarrierDemo { public static void main(String[] args) { CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{ System.out.println("葫芦小金刚出世"); }); for (int i = 0; i < 7; i++) { new Thread(()->{ System.out.println("葫芦娃老"+Thread.currentThread().getName()+"来了"); try { cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } },i==0?"大":i+1+"").start(); } } }
Semaphore
用于多个共享资源的互斥 用于并发线程数的控制
用于类似抢车位式占用资源
首先new 2个信号量,最多两个人同时用车
semaphore.acquire(); 占用信号量,最多两个线程同时占用
占用后任务执行完毕finally semaphore.release();释放信号量资源
下一个线程可以立马抢占
package com.cas; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; /** * @author liuxu * @date 2021/11/11 21:04 * */ public class SemaphoreDemo { public static void main(String[] args) { Semaphore semaphore =new Semaphore(2); for (int i = 0; i <10 ; i++) { new Thread(()->{ try { semaphore.acquire(); System.out.println(Thread.currentThread().getName()+"抢到车位"); TimeUnit.SECONDS.sleep(1); System.out.println(Thread.currentThread().getName()+"占用车位后释放"); } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); } },i+"").start(); } } }