java多线程解说【柒】_锁实现:Lock/Condition的例子

java多线程解说【伍】_锁实现:ReentrantLock的实现

java多线程解说【陆】_锁实现:Condition的实现


前两篇文章中我们分析了Reentrantlock和Condition的实现,从中我们知道,它们都是基于队列的先进先出机制,通过构建节点排队的方式完成的调度。这里还有一个问题,就是当一个节点尝试去修改公共的变量值的时候,如何保证这个修改操作的原子性和一致性,这就要说说CAS操作。


CAS机制


什么是CAS,简单来说就是Compare-And-Set。核心的思想是,当一个线程准备去修改一个公共的参数时,先取得该参数的old值,然后计算出要修改成的new值。在写入new值前,会判断old值是否和当前的值匹配,如果匹配才会把new值写到主存。


但是CAS机制无法处理ABA问题。什么是ABA问题,简单来说就是,如果线程初次读取变量的时候是A,后来被其他线程修改为B,随后又修改为A,那么当第一个线程准备赋值的时候检查到它仍然是A,此时并不能说明它的值没有被其他线程修改过。想解决这个问题,可考虑引入版本号机制。


在AQS(AbstractQueuedSynchronizer)中,就封装了如下的方法提供给线程来修改自己的属性值;


compareAndSetState
compareAndSetHead
compareAndSetTail
compareAndSetWaitStatus
compareAndSetNext


一个lock/condition的demo


首先我们可以定义一个账户类,包含账号和余额,以及存款取款接口:


public class MyCount {
    private String oid; // 账号
    private int cash; // 账户余额
    private Lock lock = new ReentrantLock(); // 账户锁
    private Condition _save = lock.newCondition(); // 存款条件
    private Condition _draw = lock.newCondition(); // 取款条件

    MyCount(String oid, int cash) {
        this.oid = oid;
        this.cash = cash;
    }

    /**
     * 存款
     * 
     * @param x
     *            操作金额
     * @param name
     *            操作人
     */
    public void saving(int x, String name) {
        lock.lock(); // 获取锁
        if (x > 0) {
            cash += x; // 存款
            System.out.println(name + "存款" + x + ",当前余额为" + cash);
        }
        _draw.signalAll(); // 唤醒所有等待线程。
        lock.unlock(); // 释放锁
    }

    /**
     * 取款
     *   
     * @param x
     *            操作金额
     * @param name
     *            操作人
     */
    public void drawing(int x, String name) {
        lock.lock(); // 获取锁
        try {
            while (cash - x < 0) { 
                _draw.await(); // 阻塞取款操作, await之后就隐示自动释放了lock,直到被唤醒自动获取

                System.out.println(name + "阻塞中");
            }
            
            cash -= x; // 取款
            System.out.println(name + "取款" + x + ",当前余额为" + cash);

            _save.signalAll(); // 唤醒所有存款操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock(); // 释放锁
        }
    }
}

如代码所示,我们针对存款和取款两个业务场景声明了两个Condition实例,都是在同一个锁下创建的。


我们声明一个存款的线程:


public class SaveThread extends Thread {
    private String name; // 操作人
    private MyCount myCount; // 账户
    private int x; // 存款金额

    SaveThread(String name, MyCount myCount, int x) {
        this.name = name;
        this.myCount = myCount;
        this.x = x;
    }

    public void run() {
        myCount.saving(x, name);
    }
}

再声明一个取款的线程:


public class DrawThread extends Thread {
    private String name; // 操作人
    private MyCount myCount; // 账户
    private int x; // 取款金额

    DrawThread(String name, MyCount myCount, int x) {
        this.name = name;
        this.myCount = myCount;
        this.x = x;
    }

    public void run() {
        myCount.drawing(x, name);
    }
}

后面我们就可以搞一个main函数了:


public static void main(String[] args) {
        // 创建并发访问的账户
        MyCount myCount = new MyCount("95599200901215522", 10000);
        // 创建一个线程池
        ExecutorService pool = Executors.newFixedThreadPool(3); //这个修改成2,可能导致老牛和胖子的死锁
        Thread t1 = new SaveThread("张三", myCount, 1000);
        Thread t2 = new SaveThread("李四", myCount, 1000);
        Thread t3 = new DrawThread("王五", myCount, 12600);
        Thread t4 = new SaveThread("老张", myCount, 600);
        Thread t5 = new DrawThread("老牛", myCount, 1300);
        Thread t6 = new DrawThread("胖子", myCount, 800);
        Thread t7 = new SaveThread("测试", myCount, 2100);
        // 执行各个线程
        pool.execute(t1);
        pool.execute(t2);
        pool.execute(t3);
        pool.execute(t4);
        pool.execute(t5);
        pool.execute(t6);
        pool.execute(t7);
        // 关闭线程池
        pool.shutdown();
    }


执行输出


当顺利的情况,控制台的日志是这样的:


李四存款1000,当前余额为11000
老张存款600,当前余额为11600
老牛取款1300,当前余额为10300
胖子取款800,当前余额为9500
测试存款2100,当前余额为11600
张三存款1000,当前余额为12600
王五取款12600,当前余额为0

有时也会这样输出:


张三存款1000,当前余额为11000
李四存款1000,当前余额为12000
老牛取款1300,当前余额为10700
老张存款600,当前余额为11300
胖子取款800,当前余额为10500
王五阻塞中
测试存款2100,当前余额为12600
王五阻塞中
王五取款12600,当前余额为0

由于王五的取款金额较大,当账户余额不足的时候就会让其等待,直到余额足够其提取。



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值