专栏首页洁癖是一只狗并发编程如何使用锁保护多个资源

并发编程如何使用锁保护多个资源

上一篇我们知道受保护资源和锁之间合理的关联关系应该是N:1的关系,也就是说一个锁可以保护多个资源,并不能多把锁来保护一个资源,今天我们就说说如何实现一把锁保护多个资源.

保护没有关联关系的多个资源

在现实生活中,球场的座位和电影院的座位是没有关联的,这种场景非常容易解决,那就是球场有球场的门票,电影院有电影的门票

同样,在编程的世界里,也是同样的原理,比如,银行业务的针对账户余额的取款操作,和银行账户密码的修改,我们可以为余额和密码分别分配不同的锁,

比如下面的代码,Account有两个成员,分别是余额balance和账户密码password,取款和查看余额会操作账户余额,我们专门创建一个final对象balLock作为锁,而更高密码我们也专门创建一个pwlock作为锁,不同的资源用不通的锁,各自管理各自的资源

class Account {
// 锁:保护账户余额
private final Object balLock
    = new Object();
// 账户余额  
private Integer balance;
// 锁:保护账户密码
private final Object pwLock
    = new Object();
// 账户密码
private String password;

// 取款
void withdraw(Integer amt) {
synchronized(balLock) {
if (this.balance > amt){
this.balance -= amt;
      }
    }
  } 
// 查看余额
Integer getBalance() {
synchronized(balLock) {
return balance;
    }
  }

// 更改密码
void updatePassword(String pw){
synchronized(pwLock) {
this.password = pw;
    }
  } 
// 查看密码
String getPassword() {
synchronized(pwLock) {
return password;
    }
  }
}

我们也可以使用一把锁把所有资源保护起来,例如我们可以用this这一把锁来管理账户所有资源,只要给多有方法添加synchronized,就可以了

但是用一把锁保护所有资源,性能方便太差,所有的资源操作都是必须串行进行的,而我们用两把锁,取款和修改密码是可以并行的,用不同锁对受保护资源进行精细化管理,能够提升性能,这个锁的名字叫细粒度锁

保护有关联关系的多个资源

多个资源有关联,是不容处理的,比如,三个账户A,B,C,我们在账户A里减少100元,给账户B加100元,这两个账户就是有关联的,看下面代码,一个账户对象,有一个成员变量余额,还有一个转账的方法transfer,如何保证转账transfer没有并发问题

class Account {
private int balance;
// 转账
void transfer(
      Account target, int amt){
if (this.balance > amt) {
this.balance -= amt;
      target.balance += amt;
    }
  } 
}

我们第一时间想到的方案就是添加synchronized,如下图

class Account {
private int balance;
// 转账
synchronized void transfer(
      Account target, int amt){
if (this.balance > amt) {
this.balance -= amt;
      target.balance += amt;
    }
  } 
}

这个方案看上去好像没有问题,但是我要知道使用synchronized其实使用this这把锁,问题就出现在this,this只能保护自己的月this.balance,却保护不了别人的余额target.balance,就像你用自己家的锁去保护别人家的锁,也不能用自己的票来保护别人的座位一样

举个例子,我们有三个账户A,B,C,余额都是200元,我们用两个线程分别操作:账户A给账户B转100元,账户B给账户C转100元,最后我们期待的是,账户A是100元,账户B是200元,账户C是300元

我们使用的两个线程,不同的CPU,这样是不能达到互斥的,因为线程1锁定的是账户A,而线程2锁定的是账户B,所以这两个线程同时进入临界区transfer,就有可能是300(线程1后于线程2,线程2写的余额值会被覆盖)也有可能是100(线程1先于线程2,线程1写的余额值会被覆盖),但是就是没有200.

使用锁的正确姿势

如果解决上面的问题呢,我们就可以使用同一把锁保护多个资源,也就是现实世界的包场,那么上面的例子中,this是对象级别的锁,但是账户A和账户B是不同的对象,如何可以共享一把锁呢

我们其实可以让所有对象都持有一个唯一性的对象,这个对象再创建Account时传入,如下面代码,我们把Account默认构造函数改成private,同时增加一个带object lock参数的构造函数,在创建Account对象是,传入相同的lock,这样所有的Account对象都共享一把锁lock

class Account {
private Object lock;
private int balance;
private Account();
// 创建Account时传入同一个lock对象
public Account(Object lock) {
this.lock = lock;
  } 
// 转账
void transfer(Account target, int amt){
// 此处检查所有对象共享的锁
    synchronized(lock) {
if (this.balance > amt) {
this.balance -= amt;
        target.balance += amt;
      }
    }
  }
}

上面是解决并发性问题,但是在实际开发中,我们是无法保证构造函数传入的是同一个锁,因为创建Account对象的代码可能分散到多个工程,传入共享lock真的很难.上面的可行性是不行的,那么我们有没有更好的方案呢,当时是有的,就是使用Account.class,而且这个对象是java虚拟机在加载Account类创建的,可以保证他就是唯一的,使用Account,class作为共享锁,修改代码如下

class Account {
private int balance;
// 转账
void transfer(Account target, int amt){
synchronized(Account.class) {
if (this.balance > amt) {
this.balance -= amt;
        target.balance += amt;
      }
    }
  } 
}

下图很直观的展示了我们是如何使用Account.class实现保护不同对象临界区

最后,我们重申一下关联关系,其实就是原子性的特征,之前我们说的原子性,主要是面向CPU指令的,转账操作的原子性是面向高级语言的,不过本质是一样

原子性的本质其实并不是不可分割,这只是他的表现,其本质是多个资源间有一致性的要求,操作的中间状态对外不可见。

本文分享自微信公众号 - 洁癖是一只狗(rookie-dog),作者:洁癖汪

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-11-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Linux常用命令(三)

    rpm -ivh --nodeeps package.rpm 安装一个rpm包而忽略依赖关系警告

    小土豆Yuki
  • Mybatis之运行原理

    Confuguration封装了多有配置文件的详细信息,把配置文件的信息解析并保存在Configuration对象中,返回包含了Confuguration的De...

    小土豆Yuki
  • 如何预防死锁

    上一篇我们使用Account.class作为互斥锁,解决了银行转账的问题,但是我们发现这样的转账操作就变成了串行,这样对于性能就会大打折扣,现实生活中这种是不能...

    小土豆Yuki
  • laravel接管Dingo-api和默认的错误处理方式

    如上图所示,AppServiceProvider.php中的register()方法中添加如下代码

    砸漏
  • Kafka中的时间轮Kafka源码分析-汇总

    将TimerTask对象绑定到 TimerTaskEntry上 如果这个TimerTask对象之前已经绑定到了一个 TimerTaskEntry上, 先调用t...

    扫帚的影子
  • 如何构建优质的推荐系统服务?| 技术头条

    任何一个优质的软件服务都必须考虑高性能、高可用(HighAvailability)、可伸缩、可拓展、安全性等5大核心要素,推荐系统也不例外。

    AI科技大本营
  • 无数学不数据,一场16年的坚守 | 数据科学50人·宣晓华

    如今,我们每个人都在谈论“数据科学”,《哈佛商业评论》杂志甚至将数据科学家定义为“21世纪最性感的职业”。在这个大数据时代,究竟什么是数据科学?数据科学家又究竟...

    DT数据侠
  • python GUI框架pyqt5 对图片进行流式布局的方法(瀑布流flowlayout)

    流式布局,也叫做瀑布流布局,是网页中经常使用的一种页面布局方式,它的原理就是将高度固定,然后图片的宽度自适应,这样加载出来的图片看起来就像瀑布一样整齐的水流淌下...

    砸漏
  • 获取URL地址中的GET参数

    似水的流年
  • 获取URL地址中的GET参数

    /*-----------------实现1--------------------*/ function getPar(par){ //获取当前URL...

    似水的流年

扫码关注云+社区

领取腾讯云代金券