首页 > 代码库 > Java 多线程分析(七)----CAS操作和阻塞
Java 多线程分析(七)----CAS操作和阻塞
1.原子性的操作:
CAS --CompareAndSwap(),指的是多个线程进入临界区域中,让多个线程在临界区域上自由的竞争,最后能够保证有一个线程能够胜出,其他没有竞争到的线程可以再一次尝试。最终临界区域上的所有线程都能够线程安全性的完成,这种方式,也叫无锁的方式,在之前的Synchronized中,不允许其他线程进入到临界区域中去进行工作。无锁的方式只能够保证线程的安全性,不同于之前讲的同步机制,因此无法对线程进行有序的调度。CAS编程最主要特色:不需要采用锁的方式对共享的资源(受保护数据)保持线程安全性(最经典的Count++)多个线程执行计数器。
在直接方法(不采用任何操作条件下)下:
输出的结果:从输出的结果中看到,有的线程输出的值是相同的:主要的原因Count++并不是一种原子类型的操作,Count++这个指令中包含了三个部分:读---修改---写入,在这个线程竞争的过程中当有的线程在读共享Count时,刚好有其他线程在这里对Count滴入,最后导致这俩个线程同时对Count进行+1,最后同时写入内存,让我们感觉输出结果一样值
pool-1-thread-3线程--3
pool-1-thread-1线程--4
pool-1-thread-4线程--4
pool-1-thread-2线程--3
pool-1-thread-4线程--7
pool-1-thread-1线程--6
pool-1-thread-3线程--5
pool-1-thread-1线程--10
pool-1-thread-1线程--12
pool-1-thread-1线程--13
pool-1-thread-4线程--9
pool-1-thread-4线程--14
pool-1-thread-4线程--15
pool-1-thread-2线程--8
pool-1-thread-2线程--16
pool-1-thread-2线程--17
pool-1-thread-2线程--18
pool-1-thread-3线程--11
pool-1-thread-3线程--19
pool-1-thread-3线程--20
解决这种方式第一种加入锁的方法:public synchronized void run()
1 pool-1-thread-3线程--1 2 pool-1-thread-3线程--2 3 pool-1-thread-3线程--3 4 pool-1-thread-3线程--4 5 pool-1-thread-3线程--5 6 pool-1-thread-1线程--6 7 pool-1-thread-1线程--7 8 pool-1-thread-1线程--8 9 pool-1-thread-1线程--9 10 pool-1-thread-1线程--10 11 pool-1-thread-2线程--11 12 pool-1-thread-2线程--12 13 pool-1-thread-2线程--13 14 pool-1-thread-2线程--14 15 pool-1-thread-2线程--15 16 pool-1-thread-4线程--16 17 pool-1-thread-4线程--17 18 pool-1-thread-4线程--18 19 pool-1-thread-4线程--19 20 pool-1-thread-4线程--20
第二中方式采用无锁的机制:采用CAS操作的指令:Atomic类中都有采用这种CAS的机制,采用的是这种非阻塞算法
原理:CAS包含3个参数:内存位置V,旧值A,新值B,当位置V的值等于A时候cAS 通过原子操作用新值B更新A的操作,用CompareAndSet(A,B)方法进行比较实现CAS操作机制。在原子操作的性质中多采用了这种方式:在循环中不断地去尝试,当成功返回值
同样在这里这个简单的加加操作,使用jdk中atomic类
1 package JavaConCurrentDemo;
2
3 import java.util.concurrent.ExecutorService;
4 import java.util.concurrent.Executors;
5 import java.util.concurrent.atomic.AtomicInteger;
6
7 public class AtomicIntegerDemo implements Runnable{
8 static AtomicInteger Count=new AtomicInteger();
9 public void run()
10 {
11 Count.incrementAndGet();
12 System.out.println(Thread.currentThread().getName()+"线程--"+Count);
13 }
14 public static void main(String[] args) {
15 // TODO Auto-generated method stub
16 CountDemo T=new CountDemo();
17 ExecutorService exce=Executors.newFixedThreadPool(4);
18 for(int i=0;i<4;i++)
19 {
20 exce.submit(T);
21 }
22
23 }
24 }
非阻塞算法:一个线程在失败或者挂起时候不应该影响其他程序的操作::atomic类只是非阻塞算法一个利用,只是简单的加加可以用atomic类实现,在复杂的算法中采用自己编译的非阻塞算法(用底层的原子机器指令(CAS之一))代替锁实现线程的安全(数据的安全性)
在这基础上采用CompareAndSet代替前面使用到Count.incrementAndGet(),得到一个不重复性的序列
1 pool-1-thread-1线程--1 2 pool-1-thread-3线程--2 3 pool-1-thread-3线程--4 4 pool-1-thread-1线程--6 5 pool-1-thread-2线程--5 6 pool-1-thread-1线程--8 7 pool-1-thread-4线程--3 8 pool-1-thread-3线程--7 9 pool-1-thread-4线程--11 10 pool-1-thread-1线程--10 11 pool-1-thread-1线程--14 12 pool-1-thread-2线程--9 13 pool-1-thread-2线程--15 14 pool-1-thread-4线程--13 15 pool-1-thread-3线程--12 16 pool-1-thread-4线程--17 17 pool-1-thread-4线程--19 18 pool-1-thread-3线程--18 19 pool-1-thread-2线程--16 20 pool-1-thread-2线程--20
1 package JavaConCurrentDemo;
2
3 import java.util.concurrent.ExecutorService;
4 import java.util.concurrent.Executors;
5 import java.util.concurrent.atomic.AtomicInteger;
6
7 public class CounterDemo {
8 public static class CasCount implements Runnable{
9 static AtomicInteger Count=new AtomicInteger(0);
10
11 public void run()
12 {
13 //用increament方法,采用copareandSet代替Count.incrementAndGet();
14 for(int i=0;i<5;i++)
15 {
16 while(true)
17 {
18 if(Count.compareAndSet(Count.get(), Count.get()+1))
19 {
20 System.out.println(Thread.currentThread().getName()+"线程--"+Count.get());
21 break;
22 }
23 }
24
25
26 }
27 }
28
29 }
30 public static void main(String[] args) {
31 // TODO Auto-generated method stub
32 ExecutorService exce=Executors.newFixedThreadPool(4);
33 for (int i=0;i<4;i++)
34 {
35 exce.submit(new CasCount());
36 }
37 }
38
39 }
采用CAS性质进行非阻塞的算法编程,在高并发量确实比锁的的性能要高,但是如果一个线程反复访问某个区域。
独占锁是一种悲观锁,synchronized就是一种独占锁,它假设最坏的情况,并且只有在确保其它线程不会造成干扰的情况下执行,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。而另一个更加有效的锁就是乐观锁。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止
这种乐观的锁叫做无锁,与加锁而言对临界区域是无障碍,通过CAS算法(用多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试)。CAS操作CPU的指令的操作,只有一步原子操作,必须要考线程安全的。
4.CAS操作的问题:
虽然CAS操作能够高效解决原子操作:三个问题:
1.循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
2.只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。如果想要保持多个变量的原子性,采用AtomicReference类来进行封装多个变量,之后对此对象进行CAS操作.
介绍一下JDk.atomic类:
atomicInteger()----主要对待整数
AtomicReference()-----对对象进行原子操作
AtomicIntegerArray[]----对整数数组
3.第三个问题ABA问题----只采用CompareAndSet()方法无法解决ABA问题,加入额外的方法AtomicStampedReference(),能够将CompareAndSet()对变量A修改的次数记录下来。
ABA问题:线程1想要将变量A的值修改成B,在此之前,出现线程2已经将变量的值由A修改成B在出现一个线程由B修改成A,(变量A的值经过了多次的修改)此时线程1在去对变量进行CAS操作发现变量的值还是A,所以CAS成功(别的线程成功),虽然值相同但是此时值但实际上这时的现场已经和最初不同了。因为在此过程发生了多次的CompareAndSet操作:
下面例子关于自动给手机充费,营业厅免费为手机充值20元一次:在此这个过程类似于ABA操作
1 package AtomicTest;
2
3 import java.util.concurrent.atomic.AtomicStampedReference;
4
5 public class AtomicStampedReferenceDemo {
6 static AtomicStampedReference<Integer> moneny=new AtomicStampedReference<Integer>(19,0);
7 public static void main(String[] args) {
8 // TODO Auto-generated method stub
9 //三个充值程序在这里监控,一个消费程序
10 for(int i=0;i<3;i++)//执行三个充值消费监控
11 { final int timestamp=moneny.getStamp();//第一个参数表示先有余额,第二个参数表示充值次数
12 new Thread()
13 {
14 //每一个进程有自己的run方法
15 public void run()
16 {
17 while (true)
18 {
19 Integer m=moneny.getReference();
20 if(m<20)
21 {
22 if(moneny.compareAndSet(m,m+20,timestamp,timestamp+1))
23 {
24 System.out.println("余额不足,自动充值20元,现有余额 "+moneny.getReference());
25 break;
26 }
27 else
28 {
29 System.out.println("以帮你充值一次,无法在送");
30 break;
31 }
32
33 }
34 else
35 {
36 break;
37 }
38 }
39 }
40 }.start();;
41 }
42 //用户模式
43 new Thread(){
44 public void run(){
45 for(int i=0;i<100;i++)
46 {
47 while(true)
48 {
49 Integer m=moneny.getReference();
50 int timestamp=moneny.getStamp();
51 if(m>10)
52 {
53 System.out.println("钱大于10元"+m);
54 if(moneny.compareAndSet(m,m-10,timestamp,timestamp+1));
55 {
56 System.out.println("消费大于10元 "+moneny.getReference());
57 break;
58 }
59
60 }
61 else
62 {//为啥*。getReference会在cas被修改
63 System.out.println("没有足够的余额");
64 break;
65 }
66 }
67 }
68 }
69 }.start();
70 }
71
72 }
Java 多线程分析(七)----CAS操作和阻塞