Java无锁的实现——原子变量

概述

对于并发控制来说,锁是一种悲观的策略。它总是假设每一次的临界区操作会产生冲突,因此,必须对每次操作都小心翼翼。如果有多个线程同时访问临界区资源,就宁可牺牲性能让线程进行等待,所以说锁会阻塞线程执行。 而无锁采用的是一种乐观的策略,它会假设对资源的访问是没有冲突的,既然没有冲突,所以不用等待。遇到冲突,无锁采用的策略是一种叫做CAS的技术来鉴别线程冲突。

CAS

CAS全称为compile and swap,它包含三个参数(V,E,N)。V表示要跟新的变量,E表示预期值,N表示新值。仅当V值等于E值的时候,才会将V的值设为N,如果V值和E值不同,则说明有其他线程做了跟新,则当前线程什么都不做。

原子变量

无锁的线程安全整数:AtomicInteger

就具体实现来说,Atomic中保存了一个核心字段

private volatile int value;

这里以具体demo的形式来给出用法,下面的几个原子变量一致。

package nolock;

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerDemo {
    static AtomicInteger i = new AtomicInteger();
    public static class AddThread implements Runnable{
        public void run(){
            for(int k = 0; k < 10000; k++){
                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);
    }
}

运行结果:

100000

无锁的对象引用:AtomicReference

同理他是对对象的引用,但是这里会有一个ABA问题。

如果A要访问的对象值初始值为a,后来线程B改为b,再后来线程C改为a,此时就产生了脏读了。所以但用这个AtomicReference解决不了问题,下面用这个例子来说明问题。

package nolock;

import java.util.concurrent.atomic.AtomicReference;

public class AtmoicReferenceDemo {
    static AtomicReference<Integer> money = new AtomicReference<Integer>();
    public static void main(String[] args){
        money.set(19);
        for(int i = 0; i < 3; i++){
            new Thread(){
                public void run(){
                    while(true){
                        while(true){
                            Integer m = money.get();
                            if(m < 20){
                                if(money.compareAndSet(m,m+20)){
                                    System.out.println("余额小于20元,充值成功,余额:" + money.get() + "元");
                                    break;
                                }
                            }else{
                                //无需充值
                                break;
                            }
                        }
                    }
                }
            }.start();

            new Thread(){
                public void run(){
                    for(int i = 0; i < 100; i++){
                        while (true){
                            Integer m = money.get();
                            if(m > 10){
                                System.out.println("大于10元");
                                if(money.compareAndSet(m,m-10)){
                                    System.out.println("成功消费10元,余额:" + money.get());
                                    break;
                                }
                            }else{
                                System.out.println("没有足够的金额");
                                break;
                            }
                        }
                        try{
                            Thread.sleep(100);
                        }catch (InterruptedException e){
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
        }
    }
}

这个例子的结果会无限循环下去,余额值会一直浮动变化。

那么用什么来解决呢?

加一个时间戳stamp

AtomicStampedReference

带有时间戳的对象引用:AtomicStampedReference

package nolock;

import java.util.concurrent.atomic.AtomicStampedReference;

public class AtomicStampedReferenceDemo {
    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{
                                //无需充值
                                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){
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }
}

用这个方法余额就只会赠予一次。

数组也能无锁:AtomicIntegerArray

package nolock;

import java.util.concurrent.atomic.AtomicIntegerArray;

public class AtomicIntegerArrayDemo {
    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);
    }
}

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

package nolock;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class AtomicIntegerFieldUpdaterDemo {
    public static class Candidate{
        int id;
        volatile int score;//必须为volatile,并且不能private,也不能用static修饰
    }
    public final static AtomicIntegerFieldUpdater<Candidate> scoreUpdater
            = AtomicIntegerFieldUpdater.newUpdater(Candidate.class,"score");
    public static AtomicInteger allScore = new AtomicInteger(0);
    public static void main(String[] args)throws InterruptedException{
        final Candidate stu = new Candidate();
        Thread[] t = new Thread[10000];
        for(int i = 0; i < 10000; i++){
            t[i] = new Thread(){
                public void run(){
                    if(Math.random() > 0.4){
                        scoreUpdater.incrementAndGet(stu);
                        allScore.incrementAndGet();
                    }
                }
            };
            t[i].start();
        }
        for(int i = 0; i < 10000; i++){
            t[i].join();
        }
        System.out.println("score = "+ stu.score);
        System.out.println("allScore = " + allScore);
    }
}

因为AtomicIntegerFieldUpdater保证了Candidate.score的线程安全,所以Candidate.score的值总是和allscore的值相等。

AtomicIntegerFieldUpdater的使用注意事项:

1.必须为volatile

2.不能private(因为Updater是使用反射得到这个变量的,如果变量不可见就会出错)

3.不能用static修饰(因为Unsafe.objectFieldOffset()不支持静态变量)

参考文献:《Java高并发程序设计》

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值