首页 > 代码库 > Java 多线程分析(七)----CAS操作和阻塞

Java 多线程分析(七)----CAS操作和阻塞

1.原子性的操作:

  CAS --CompareAndSwap(),指的是多个线程进入临界区域中,让多个线程在临界区域上自由的竞争,最后能够保证有一个线程能够胜出,其他没有竞争到的线程可以再一次尝试。最终临界区域上的所有线程都能够线程安全性的完成,这种方式,也叫无锁的方式,在之前的Synchronized中,不允许其他线程进入到临界区域中去进行工作。无锁的方式只能够保证线程的安全性,不同于之前讲的同步机制,因此无法对线程进行有序的调度。CAS编程最主要特色:不需要采用锁的方式对共享的资源(受保护数据)保持线程安全性(最经典的Count++)多个线程执行计数器。

  在直接方法(不采用任何操作条件下)下:

 

技术分享View Code

 

输出的结果:从输出的结果中看到,有的线程输出的值是相同的:主要的原因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操作和阻塞