Java高并发程序设计笔记4之无锁

无锁类的原理

CAS(compare and swap)

算法的过程是这样:它包含3个参数CAS(V,E,N)。V表示要更新的变量,E表示预期值,N表示新值。仅当V 值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么 都不做。最后,CAS返回当前V的真实值。CAS操作是抱着乐观的态度进行的,它总是认为自己可以成功完成 操作。当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程 不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理,CAS
操作即时没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。

CPU指令

CPU指令cmpxchg

 /* accumulator = AL, AX, or EAX, depending on whether 

  a byte, word, or doubleword comparison is being performed

 */ if(accumulator == Destination) 

   { 

ZF = 1;

  Destination = Source; 

   } else { 

        ZF = 0;

        accumulator = Destination; 

   }

AtomicInteger简介

这个类真的非常实用,更重要的是 它确实非常简单:
AtomicInteger,一个提供原子操作的Integer的类。在Java语言中,++i和i++操作并不是线程安全的,在使用的时候,不可避免的会用到synchronized关键字。而AtomicInteger则通过一种线程安全的加减操作接口。

主要接口

 public final int get() //取得当前值

 public final void set(int newValue) //设置当前值 

 public final int getAndSet(int newValue) //设置新值,并返回旧值

 public final boolean compareAndSet(int expect, int u) //如果当前值为expect,则设置为u

 public final int getAndIncrement() //当前值加1,返回旧值 

 public final int getAndDecrement() //当前值减1,返回旧值 

 public final int getAndAdd(int delta) //当前值增加delta,返回旧值 

 public final int incrementAndGet() //当前值加1,返回新值 

 public final int decrementAndGet() //当前值减1,返回新值 

 public final int addAndGet(int delta) //当前值增加delta,返回新值

下面是测试代码,如下

public class AtomicIntegerDemo1 {
    static AtomicInteger i=new AtomicInteger();
    public static class AddThread implements Runnable{
        public void run(){
            i.incrementAndGet();
        }
    }
    public static void main(String args[])throws InterruptedException{
        Thread[] ts= new Thread[10];
        for(int k=0;k<10;k++){
            ts[k] = new Thread(new AddThread());
        }
        for(int k=0;k<10;k++){
            ts[k].start();
        }
        for(int k=0;k<10;k++){
            ts[k].join();
        }
        System.out.println(i);
    }
}

public class AtomicIntegerDemo2 {
    public static void main(String[] args) {
        AtomicInteger ai=new AtomicInteger(0);
        int i1=ai.get();
        v(i1);
        int i2=ai.getAndSet(5);
        v(i2);
        int i3=ai.get();
        v(i3);
        int i4=ai.getAndIncrement();
        v(i4);
        v(ai.get());

    }
    static void v(int i)
    {
        System.out.println("i : "+i);
    }
Unsafe

非安全的操作,比如:根据偏移量设置值 park() 底层的CAS操作 非公开API,在不同版本的JDK中, 可能有较大差异
unsafe主要用于jdk内部,一般外部使用不到,一般根据偏移量来设置数据
底层的cas操作 在jdk内部大量使用,在一些第三方的高性能的第三方框架也会使用unsafe

//获得给定对象偏移量上的int值

 public native int getInt(Object o, long offset); 

//设置给定对象偏移量上的int值 

public native void putInt(Object o, long offset, int x); 

//获得字段在对象中的偏移量 

public native long objectFieldOffset(Field f);

 //设置给定对象的int值,使用volatile语义

 public native void putIntVolatile(Object o, long offset, int x); 

//获得给定对象对象的int值,使用volatile语义 

public native int     getIntVolatile(Object o, long offset); 

//和putIntVolatile()一样,但是它要求被操作字段就是volatile类型的

 public native void putOrderedInt(Object o, long offset, int x);

AtomicReference

对引用进行修改是一个模板类,抽象化了数据类型

AtomicReference是作用是对"对象"进行原子操作。

get()

set(V) 

compareAndSet() 

getAndSet(V)

AtomicReference的源码比较简单。

 它是通过"volatile"和"Unsafe提供的CAS函数实现"原子操作。
        (1) value是volatile类型。这保证了:当某线程修改value的值时,其他线程看到的value值都是最新的value值,即修改之后的volatile的值。
        (2) 通过CAS设置value。这保证了:当某线程池通过CAS函数(如compareAndSet函数)设置value时,它的操作是原子的,即线程在操作value时不会被中断。

例如下面的代码

public class AtomicReferenceTest2 {
    public static void main(String[] args){

        // 创建两个Person对象,它们的id分别是101和102。
        Person p1 = new Person(101);
        Person p2 = new Person(102);
        // 新建AtomicReference对象,初始化它的值为p1对象
        AtomicReference ar = new AtomicReference(p1);
        // 通过CAS设置ar。如果ar的值为p1的话,则将其设置为p2。
        ar.compareAndSet(p1, p2);

        Person p3 = (Person)ar.get();
        System.out.println("p3 is "+p3);
        System.out.println("p3.equals(p1)="+p3.equals(p1));
    }
}

class Person {
    volatile long id;
    public Person(long id) {
        this.id = id;
    }
    public String toString() {
        return "id:"+id;
    }
}
运行结果:
p3 is id:102
p3.equals(p1)=false

        结果说明:
        新建AtomicReference对象ar时,将它初始化为p1。
        紧接着,通过CAS函数对它进行设置。如果ar的值为p1的话,则将其设置为p2。
        最后,获取ar对应的对象,并打印结果。p3.equals(p1)的结果为false,这是因为Person并没有覆盖equals()方法,而是采用继承自Object.java的equals()方法;而Object.java中的equals()实际上是调用"=="去比较两个对象,即比较两个对象的地址是否相等。
再比如

package com.cosmistar.bean;

import java.util.concurrent.atomic.AtomicReference;

/**
 * Created by Administrator on 2016/11/12.
 */
public class AtomicReferenceTest1 {
    final static AtomicReference<String>  atomicString =new AtomicReference<String>("love");
    public static void main(String args[]){
        for(int i=0;i<10;i++){
            new Thread(){
                public void run(){
                    try{
                        Thread.sleep(Math.abs((int)(Math.random()*100)));
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                    if(atomicString.compareAndSet("love","defa")){
                        System.out.println("Thread:"+Thread.currentThread().getId()+"change value");
                    }else{
                        System.out.println("Thread:"+Thread.currentThread().getId()+"failed");
                    }
                }

            }.start();
        }
    }
}

AtomicStampedReference

主要解决ABA问题

AtomicStampedReference它内部不仅维护了对象值,还维护了一个时间戳(我这里把它称为时间戳,实际上它可以使任何一个整数,它使用整数来表示状态值)。当AtomicStampedReference对应的数值被修改时,除了更新数据本身外,还必须要更新时间戳。当AtomicStampedReference设置对象值时,对象值以及时间戳都必须满足期望值,写入才会成功。因此,即使对象值被反复读写,写回原值,只要时间戳发生变化,就能防止不恰当的写入。

 //比较设置 参数依次为:期望值 写入新值 期望时间戳 新时间戳

 public boolean compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp)

 //获得当前对象引用 

 public V getReference() 

 //获得当前时间戳 

 public int getStamp()

  //设置当前对象引用和时间戳 

  public void set(V newReference, int newStamp)

package com.cosmistar.bean;

import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * Created by Administrator on 2016/11/12.
 */
public class AtomicStampedReferenceDemo1 {
    static AtomicStampedReference<Integer> money=new AtomicStampedReference<Integer>(19,0);
   public static void main(String[] args) {
                //模拟多个线程同时更新后台数据库,为用户充值
                for(int i = 0 ; i < 3 ; i++) {
                       final int timestamp=money.getStamp();
                         new Thread() {
                                public void run() {
                                       while(true){
                                               while(true){
                                                       Integer m=money.getReference();
                                                       if(m<20){
                                                           if(money.compareAndSet(m,m+20,timestamp,timestamp+1)){
                                                                         System.out.println("余额小于20元,充值成功,余额:"+money.getReference()+"元");
                                                                         break;
                                                                     }
                                                             }else{
                                                                //System.out.println("余额大于20元,无需充值");
                                                                 break ;
                                                             }
                                                    }
                                            }
                                    }
                            }.start();
                     }

                //用户消费线程,模拟消费行为
       new Thread() {
             public void run() {
                               for(int i=0;i<100;i++){
                                       while(true){
                                              int timestamp=money.getStamp();
                                              Integer m=money.getReference();
                                              if(m>10){
                                                        System.out.println("大于10元");
                                                        if(money.compareAndSet(m, m-10,timestamp,timestamp+1)){
                                                       System.out.println("成功消费10元,余额:"+money.getReference());
                                                                 break;
                                                             }
                                                    }else{
                                                        System.out.println("没有足够的金额");
                                                        break;
                                                   }
                                            }
                                        try {Thread.sleep(100);} catch (InterruptedException e) {}
                                     }
                            }
      }.start();
   }
 }

执行上述代码,可以得到以下输出:
余额小于20元,充值成功,余额:39元
大于10元
成功消费10元,余额:29
大于10元
成功消费10元,余额:19
大于10元
成功消费10元,余额:9
没有足够的金额
可以看到,账户只被赠予了一次。
AtomicIntegerArray

支持无锁的数组,除了提供基本数据类型外,JDK还为我们准备了数组等复合结构。当前可用的原子数组有:AtomicIntegerArray、AtomicLongArray和AtomicReferenceArray,分别表示整数数组、long型数组和普通的对象数组。

这里以AtomicIntegerArray为例,展示原子数组的使用方式。

//获得数组第i个下标的元素 

public final int get(int i)

 //获得数组的长度 public final int length() 

//将数组第i个下标设置为newValue,并返回旧的值 

public final int getAndSet(int i, int newValue)

 //进行CAS操作,如果第i个下标的元素等于expect,则设置为update,设置成功返回true 

public final boolean compareAndSet(int i, int expect, int update) 

//将第i个下标的元素加1 

public final int getAndIncrement(int i) //将第i个下标的元素减1 

public final int getAndDecrement(int i) //将第i个下标的元素增加delta(delta可以是负数) 

public final int getAndAdd(int i, int delta)

package com.cosmistar.bean;

import java.util.concurrent.atomic.AtomicIntegerArray;

/**
 * Created by Administrator on 2016/11/12.
 */
public class AtomicIntegerArrayDemo1 {
    static AtomicIntegerArray arr = new AtomicIntegerArray(10);
     public static class AddThread implements Runnable{
                 public void run(){
                        for(int k=0;k<10000;k++)
                                arr.getAndIncrement(k%arr.length());
                     }
             }
    public static void main(String[] args) throws InterruptedException {
                 Thread[]ts=new Thread[10];
                 for(int k=0;k<10;k++){
                        ts[k]=new Thread(new AddThread());
                    }
                 for(int k=0;k<10;k++){ts[k].start();}
                 for(int k=0;k<10;k++){ts[k].join();}
                System.out.println(arr);
            }
}
上述代码第2行,申明了一个内含10个元素的数组。第3行定义的线程对数组内10个元素进行累加操作,每个元素各加1000次。第11行,开启10个这样的线程。因此,可以预测,如果线程安全,数组内10个元素的值必然都是10000。反之,如果线程不安全,则部分或者全部数值会小于10000。


程序的输出结果如下:

[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]

这说明AtomicIntegerArray确实合理地保证了数组的线程安全性。
AtomicIntegerFieldUpdater

让普通变量也享受原子操作

1. Updater只能修改它可见范围内的变量。因为Updater使用反射得到这个变量。如果变量不可见,就会出错。 比如如果score申明为private,就是不可行的。 

2. 为了确保变量被正确的读取,它必须是volatile类型的。如果我们原有代码中未申明这个类型,那么简单得
申明一下就行,这不会引起什么问题。
3. 由于CAS操作会通过对象实例中的偏移量直接进行赋值,因此,它不支持static字段(Unsafe. objectFieldOffset()不支持静态变量)。

 AtomicIntegerFieldUpdater.newUpdater()

 incrementAndGet()

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值