zoukankan      html  css  js  c++  java
  • 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 }
  • 相关阅读:
    Android Button的四种点击事件
    Android中StateListDrawable的种类(状态的种类)
    Android中StateListDrawable的种类(状态的种类)
    信息系统项目管理师 高级 初始 寻挂靠
    lib 和 dll 的区别、生成以及使用详解
    如何为WPF添加Main()函数 程序入口点的修改
    pragma pack(非常有用的字节对齐用法说明)
    MFC DestroyWindow窗口对象和窗口句柄的销毁
    VS2008生成的程序无法在其它电脑上运行,提示系统无法执行指定的程序
    MFC修改任务栏图标及程序运行exe图标
  • 原文地址:https://www.cnblogs.com/woainifanfan/p/6602696.html
Copyright © 2011-2022 走看看