当前位置: 首页 > news >正文

网站建设课程设计摘要全球邮企业邮箱

网站建设课程设计摘要,全球邮企业邮箱,网站建设公司发展规划,自己写代码做网站大家好#xff0c;我是易安! 我们知道一个或者多个操作在CPU执行的过程中不被中断的特性#xff0c;称为“原子性”。理解这个特性有助于你分析并发编程Bug出现的原因#xff0c;例如利用它可以分析出long型变量在32位机器上读写可能出现的诡异Bug#xff0c;明明已经把变量… 大家好我是易安! 我们知道一个或者多个操作在CPU执行的过程中不被中断的特性称为“原子性”。理解这个特性有助于你分析并发编程Bug出现的原因例如利用它可以分析出long型变量在32位机器上读写可能出现的诡异Bug明明已经把变量成功写入内存重新读出来却不是自己写入的。 那原子性问题到底该如何解决呢 原子性问题的源头是 线程切换如果能够禁用线程切换那不就能解决这个问题了吗而操作系统做线程切换是依赖CPU中断的所以禁止CPU发生中断就能够禁止线程切换。 在早期单核CPU时代这个方案的确是可行的而且也有很多应用案例但是并不适合多核场景。这里我们以32位CPU上执行long型变量的写操作为例来说明这个问题long型变量是64位在32位CPU上执行写操作会被拆分成两次写操作写高32位和写低32位如下图所示。 在单核CPU场景下同一时刻只有一个线程执行禁止CPU中断意味着操作系统不会重新调度线程也就是禁止了线程切换获得CPU使用权的线程就可以不间断地执行所以两次写操作一定是要么都被执行要么都没有被执行具有原子性。 但是在多核场景下同一时刻有可能有两个线程同时在执行一个线程执行在CPU-1上一个线程执行在CPU-2上此时禁止CPU中断只能保证CPU上的线程连续执行并不能保证同一时刻只有一个线程执行如果这两个线程同时写long型变量高32位的话那就有可能出现我们开头提及的诡异Bug了。 “ 同一时刻只有一个线程执行”这个条件非常重要我们称之为 互斥。如果我们能够保证对共享变量的修改是互斥的那么无论是单核CPU还是多核CPU就都能保证原子性了。 简易锁模型 当谈到互斥相信你一定想到了那个杀手级解决方案锁。同时大脑中还会出现以下模型 简易锁模型 我们把一段需要互斥执行的代码称为 临界区。线程在进入临界区之前首先尝试加锁lock()如果成功则进入临界区此时我们称这个线程持有锁否则呢就等待直到持有锁的线程解锁持有锁的线程执行完临界区的代码后执行解锁unlock()。 这个过程非常像办公室里高峰期抢占坑位每个人都是进坑锁门加锁出坑开门解锁如厕这个事就是临界区。很长时间里我也是这么理解的。这样理解本身没有问题但却很容易让我们忽视两个非常非常重要的点我们锁的是什么我们保护的又是什么 改进后的锁模型 我们知道在现实世界里锁和锁要保护的资源是有对应关系的比如你用你家的锁保护你家的东西我用我家的锁保护我家的东西。在并发编程世界里锁和资源也应该有这个关系但这个关系在我们上面的模型中是没有体现的所以我们需要完善一下我们的模型。 改进后的锁模型 首先我们要把临界区要保护的资源标注出来如图中临界区里增加了一个元素受保护的资源R其次我们要保护资源R就得为它创建一把锁LR最后针对这把锁LR我们还需在进出临界区时添上加锁操作和解锁操作。另外在锁LR和受保护资源之间我特地用一条线做了关联这个关联关系非常重要。很多并发Bug的出现都是因为把它忽略了然后就出现了类似锁自家门来保护他家资产的事情这样的Bug非常不好诊断因为潜意识里我们认为已经正确加锁了。 JDK提供的锁synchronized 锁是一种通用的技术方案Java语言提供的synchronized关键字就是锁的一种实现。synchronized关键字可以用来修饰方法也可以用来修饰代码块它的使用示例基本上都是下面这个样子 class X {  // 修饰非静态方法  synchronized void foo() {    // 临界区  }  // 修饰静态方法  synchronized static void bar() {    // 临界区  }  // 修饰代码块  Object obj  new Object()  void baz() {    synchronized(obj) {      // 临界区    }  }} 看完之后你可能会觉得有点奇怪这个和我们上面提到的模型有点对不上号啊加锁lock()和解锁unlock()在哪里呢其实这两个操作都是有的只是这两个操作是被Java默默加上的Java编译器会在synchronized修饰的方法或代码块前后自动加上加锁lock()和解锁unlock()这样做的好处就是加锁lock()和解锁unlock()一定是成对出现的毕竟忘记解锁unlock()可是个致命的Bug意味着其他线程只能死等下去了。 那synchronized里的加锁lock()和解锁unlock()锁定的对象在哪里呢上面的代码我们看到只有修饰代码块的时候锁定了一个obj对象那修饰方法的时候锁定的是什么呢这个也是Java的一条隐式规则 当修饰静态方法的时候锁定的是当前类的Class对象在上面的例子中就是Class X 当修饰非静态方法的时候锁定的是当前实例对象this。 对于上面的例子synchronized修饰静态方法相当于: class X {  // 修饰静态方法  synchronized(X.class) static void bar() {    // 临界区  }} 修饰非静态方法相当于 class X {  // 修饰非静态方法  synchronized(this) void foo() {    // 临界区  }} 用synchronized解决count1问题 相信你一定记得我们前面文章中提到过的count1存在的并发问题现在我们可以尝试用synchronized来小试牛刀一把代码如下所示。SafeCalc这个类有两个方法一个是get()方法用来获得value的值另一个是addOne()方法用来给value加1并且addOne()方法我们用synchronized修饰。那么我们使用的这两个方法有没有并发问题呢 class SafeCalc {  long value  0L;  long get() {    return value;  }  synchronized void addOne() {    value  1;  }} 我们先来看看addOne()方法首先可以肯定被synchronized修饰后无论是单核CPU还是多核CPU只有一个线程能够执行addOne()方法所以一定能保证原子操作那是否有可见性问题呢这里我们不得不提到 管程中锁的规则。 管程中锁的规则对一个锁的解锁 Happens-Before 于后续对这个锁的加锁。 管程就是我们这里的synchronized至于为什么叫管程我们后面介绍我们知道synchronized修饰的临界区是互斥的也就是说同一时刻只有一个线程执行临界区的代码而所谓“对一个锁解锁 Happens-Before 后续对这个锁的加锁”指的是前一个线程的解锁操作对后一个线程的加锁操作可见综合Happens-Before的传递性原则我们就能得出前一个线程在临界区修改的共享变量该操作在解锁之前对后续进入临界区该操作在加锁之后的线程是可见的。 按照这个规则如果多个线程同时执行addOne()方法可见性是可以保证的也就说如果有1000个线程执行addOne()方法最终结果一定是value的值增加了1000。看到这个结果我们长出一口气问题终于解决了。 但也许你一不小心就忽视了get()方法。执行addOne()方法后value的值对get()方法是可见的吗这个可见性是没法保证的。管程中锁的规则是只保证后续对这个锁的加锁的可见性而get()方法并没有加锁操作所以可见性没法保证。那如何解决呢很简单就是get()方法也synchronized一下完整的代码如下所示。 class SafeCalc {  long value  0L;  synchronized long get() {    return value;  }  synchronized void addOne() {    value  1;  }} 上面的代码转换为我们提到的锁模型就是下面图示这个样子。get()方法和addOne()方法都需要访问value这个受保护的资源这个资源用this这把锁来保护。线程要进入临界区get()和addOne()必须先获得this这把锁这样get()和addOne()也是互斥的。 保护临界区get()和addOne()的示意图 这个模型更像现实世界里面球赛门票的管理一个座位只允许一个人使用这个座位就是“受保护资源”球场的入口就是Java类里的方法而门票就是用来保护资源的“锁”Java里的检票工作是由synchronized解决的。 锁和受保护资源的关系 我们前面提到受保护资源和锁之间的关联关系非常重要他们的关系是怎样的呢一个合理的关系是 受保护资源和锁之间的关联关系是N:1的关系。拿球赛门票的管理来类比一个座位我们只能用一张票来保护如果多发了重复的票那就要打架了。现实世界里我们可以用多把锁来保护同一个资源但在并发领域是不行的并发领域的锁和现实世界的锁不是完全匹配的。不过倒是可以用同一把锁来保护多个资源这个对应到现实世界就是我们所谓的“包场”了。 上面那个例子我稍作改动把value改成静态变量把addOne()方法改成静态方法此时get()方法和addOne()方法是否存在并发问题呢 class SafeCalc {  static long value  0L;  synchronized long get() {    return value;  }  synchronized static void addOne() {    value  1;  }} 如果你仔细观察就会发现改动后的代码是用两个锁保护一个资源。这个受保护的资源就是静态变量value两个锁分别是this和SafeCalc.class。我们可以用下面这幅图来形象描述这个关系。由于临界区get()和addOne()是用两个锁保护的因此这两个临界区没有互斥关系临界区addOne()对value的修改对临界区get()也没有可见性保证这就导致并发问题了。 两把锁保护一个资源的示意图 刚刚我们谈过 受保护资源和锁之间合理的关联关系应该是N:1的关系也就是说可以用一把锁来保护多个资源但是不能用多把锁来保护一个资源并且结合文中示例我们也重点强调了“不能用多把锁来保护一个资源”这个问题。而至于如何保护多个资源我们下面就来聊聊。 当我们要保护多个资源时首先要区分这些资源是否存在关联关系。 保护没有关联关系的多个资源 在现实世界里球场的座位和电影院的座位就是没有关联关系的这种场景非常容易解决那就是球赛有球赛的门票电影院有电影院的门票各自管理各自的。 同样这对应到编程领域也很容易解决。例如银行业务中有针对账户余额余额是一种资源的取款操作也有针对账户密码密码也是一种资源的更改操作我们可以为账户余额和账户密码分配不同的锁来解决并发问题这个还是很简单的。 相关的示例代码如下账户类Account有两个成员变量分别是账户余额balance和账户密码password。取款withdraw()和查看余额getBalance()操作会访问账户余额balance我们创建一个final对象balLock作为锁类比球赛门票而更改密码updatePassword()和查看密码getPassword()操作会修改账户密码password我们创建一个final对象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减少100元账户B增加100元。这两个账户就是有关联关系的。那对于像转账这种有关联关系的操作我们应该怎么去解决呢先把这个问题代码化。我们声明了个账户类Account该类有一个成员变量余额balance还有一个用于转账的方法transfer()然后怎么保证转账操作transfer()没有并发问题呢 class Account {  private int balance;  // 转账  void transfer(      Account target, int amt){    if (this.balance  amt) {      this.balance - amt;      target.balance  amt;    }  }} 相信你的直觉会告诉你这样的解决方案用户synchronized关键字修饰一下transfer()方法就可以了于是你很快就完成了相关的代码如下所示。 class Account {  private int balance;  // 转账  synchronized void transfer(      Account target, int amt){    if (this.balance  amt) {      this.balance - amt;      target.balance  amt;    }  }} 在这段代码中临界区内有两个资源分别是转出账户的余额this.balance和转入账户的余额target.balance并且用的是一把锁this符合我们前面提到的多个资源可以用一把锁来保护这看上去完全正确呀。真的是这样吗可惜这个方案仅仅是看似正确为什么呢 问题就出在this这把锁上this这把锁可以保护自己的余额this.balance却保护不了别人的余额target.balance就像你不能用自家的锁来保护别人家的资产也不能用自己的票来保护别人的座位一样。 用锁this保护this.balance和target.balance的示意图 下面我们具体分析一下假设有A、B、C三个账户余额都是200元我们用两个线程分别执行两个转账操作账户A转给账户B 100 元账户B转给账户C 100 元最后我们期望的结果应该是账户A的余额是100元账户B的余额是200元 账户C的余额是300元。 我们假设线程1执行账户A转账户B的操作线程2执行账户B转账户C的操作。这两个线程分别在两颗CPU上同时执行那它们是互斥的吗我们期望是但实际上并不是。因为线程1锁定的是账户A的实例A.this而线程2锁定的是账户B的实例B.this所以这两个线程可以同时进入临界区transfer()。同时进入临界区的结果是什么呢线程1和线程2都会读到账户B的余额为200导致最终账户B的余额可能是300线程1后于线程2写B.balance线程2写的B.balance值被线程1覆盖可能是100线程1先于线程2写B.balance线程1写的B.balance值被线程2覆盖就是不可能是200。 并发转账示意图 使用锁的正确姿势 刚刚我们提到用同一把锁来保护多个资源也就是现实世界的“包场”那在编程领域应该怎么“包场”呢很简单只要我们的 锁能覆盖所有受保护资源 就可以了。在上面的例子中this是对象级别的锁所以A对象和B对象都有自己的锁如何让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对象的时候必须传入同一个对象如果创建Account对象时传入的lock不是同一个对象那可就惨了会出现锁自家门来保护他家资产的荒唐事。在真实的项目场景中创建Account对象的代码很可能分散在多个工程中传入共享的lock真的很难。 所以上面的方案缺乏实践的可行性我们需要更好的方案。还真有就是 用Account.class作为共享的锁。Account.class是所有Account对象共享的而且这个对象是Java虚拟机在加载Account类的时候创建的所以我们不用担心它的唯一性。使用Account.class作为共享的锁我们就无需在创建Account对象时传入了代码更简单。 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来保护不同对象的临界区的。 这个方案不存在并发问题但是所有账户的转账操作都是串行的例如账户A 转账户B、账户C 转账户D这两个转账操作现实世界里是可以并行的但是在这个方案里却被串行化了这样的话性能太差。现实世界里账户转账操作是支持并发的而且绝对是真正的并行银行所有的窗口都可以做转账操作。只要我们能仿照现实世界做转账操作串行的问题就解决了。 我们试想在古代没有信息化账户的存在形式真的就是一个账本而且每个账户都有一个账本这些账本都统一存放在文件架上。银行柜员在给我们做转账时要去文件架上把转出账本和转入账本都拿到手然后做转账。这个柜员在拿账本的时候可能遇到以下三种情况 文件架上恰好有转出账本和转入账本那就同时拿走 如果文件架上只有转出账本和转入账本之一那这个柜员就先把文件架上有的账本拿到手同时等着其他柜员把另外一个账本送回来 转出账本和转入账本都没有那这个柜员就等着两个账本都被送回来。 上面这个过程如何用编程实现呢其实用两把锁就实现了转出账本一把转入账本另一把。在transfer()方法内部我们首先尝试锁定转出账户this先把转出账本拿到手然后尝试锁定转入账户target再把转入账本拿到手只有当两者都成功时才执行转账操作。这个逻辑可以图形化为下图这个样子。 两个转账操作并行示意图 而至于详细的代码实现如下所示。经过这样的优化后账户A 转账户B和账户C 转账户D这两个转账操作就可以并行了。 class Account {  private int balance;  // 转账  void transfer(Account target, int amt){    // 锁定转出账户    synchronized(this) {      // 锁定转入账户      synchronized(target) {        if (this.balance  amt) {          this.balance - amt;          target.balance  amt;        }      }    }  }} 死锁的产生 上面的实现看上去很完美并且也算是将锁用得出神入化了。相对于用Account.class作为互斥锁锁定的范围太大而我们锁定两个账户范围就小多了这样的锁叫 细粒度锁。 使用细粒度锁可以提高并行度是性能优化的一个重要手段。 但是使用细粒度锁是有代价的这个代价就是可能会导致死锁。 转账业务中的“死等” 现实世界里的死等就是编程领域的死锁了。 死锁 的一个比较专业的定义是 一组互相竞争资源的线程因互相等待导致“永久”阻塞的现象。 上面转账的代码是怎么发生死锁的呢我们假设线程T1执行账户A转账户B的操作账户A.transfer(账户B)同时线程T2执行账户B转账户A的操作账户B.transfer(账户A)。当T1和T2同时执行完①处的代码时T1获得了账户A的锁对于T1this是账户A而T2获得了账户B的锁对于T2this是账户B。之后T1和T2在执行②处的代码时T1试图获取账户B的锁时发现账户B已经被锁定被T2锁定所以T1开始等待T2则试图获取账户A的锁时发现账户A已经被锁定被T1锁定所以T2也开始等待。于是T1和T2会无期限地等待下去也就是我们所说的死锁了。 class Account {  private int balance;  // 转账  void transfer(Account target, int amt){    // 锁定转出账户    synchronized(this){     ①      // 锁定转入账户      synchronized(target){ ②        if (this.balance  amt) {          this.balance - amt;          target.balance  amt;        }      }    }  }} 关于这种现象我们还可以借助资源分配图来可视化锁的占用情况资源分配图是个有向图它可以描述资源和线程的状态。其中资源用方形节点表示线程用圆形节点表示资源中的点指向线程的边表示线程已经获得该资源线程指向资源的边则表示线程请求资源但尚未得到。转账发生死锁时的资源分配图就如下图所示一个“各据山头死等”的尴尬局面。 转账发生死锁时的资源分配图 如何预防死锁 并发程序一旦死锁一般没有特别好的方法很多时候我们只能重启应用。因此解决死锁问题最好的办法还是规避死锁。 那如何避免死锁呢要避免死锁就需要分析死锁发生的条件有个叫Coffman的牛人早就总结过了只有以下这四个条件都发生时才会出现死锁 互斥共享资源X和Y只能被一个线程占用 占有且等待线程T1已经取得共享资源X在等待共享资源Y的时候不释放共享资源X 不可抢占其他线程不能强行抢占线程T1占有的资源 循环等待线程T1等待线程T2占有的资源线程T2等待线程T1占有的资源就是循环等待。 反过来分析 也就是说只要我们破坏其中一个就可以成功避免死锁的发生。 其中互斥这个条件我们没有办法破坏因为我们用锁为的就是互斥。不过其他三个条件都是有办法破坏掉的到底如何做呢 对于“占用且等待”这个条件我们可以一次性申请所有的资源这样就不存在等待了。 对于“不可抢占”这个条件占用部分资源的线程进一步申请其他资源时如果申请不到可以主动释放它占有的资源这样不可抢占这个条件就破坏掉了。 对于“循环等待”这个条件可以靠按序申请资源来预防。所谓按序申请是指资源是有线性顺序的申请的时候可以先申请资源序号小的再申请资源序号大的这样线性化后自然就不存在循环了。 我们已经从理论上解决了如何预防死锁那具体如何体现在代码上呢下面我们就来尝试用代码实践一下这些理论。 1. 破坏占用且等待条件 从理论上讲要破坏这个条件可以一次性申请所有资源。在现实世界里就拿前面我们提到的转账操作来讲它需要的资源有两个一个是转出账户另一个是转入账户当这两个账户同时被申请时我们该怎么解决这个问题呢 可以增加一个账本管理员然后只允许账本管理员从文件架上拿账本也就是说柜员不能直接在文件架上拿账本必须通过账本管理员才能拿到想要的账本。例如张三同时申请账本A和B账本管理员如果发现文件架上只有账本A这个时候账本管理员是不会把账本A拿下来给张三的只有账本A和B都在的时候才会给张三。这样就保证了“一次性申请所有资源”。 通过账本管理员拿账本 对应到编程领域“同时申请”这个操作是一个临界区我们也需要一个角色Java里面的类来管理这个临界区我们就把这个角色定为Allocator。它有两个重要功能分别是同时申请资源apply()和同时释放资源free()。账户Account 类里面持有一个Allocator的单例必须是单例只能由一个人来分配资源。当账户Account在执行转账操作的时候首先向Allocator同时申请转出账户和转入账户这两个资源成功后再锁定这两个资源当转账操作执行完释放锁之后我们需通知Allocator同时释放转出账户和转入账户这两个资源。具体的代码实现如下。 class Allocator {  private ListObject als     new ArrayList();  // 一次性申请所有资源  synchronized boolean apply(    Object from, Object to){    if(als.contains(from) ||         als.contains(to)){      return false;    } else {      als.add(from);      als.add(to);    }    return true;  }  // 归还资源  synchronized void free(    Object from, Object to){    als.remove(from);    als.remove(to);  }}class Account {  // actr应该为单例  private Allocator actr;  private int balance;  // 转账  void transfer(Account target, int amt){    // 一次性申请转出账户和转入账户直到成功    while(!actr.apply(this, target))          try{      // 锁定转出账户      synchronized(this){        // 锁定转入账户        synchronized(target){          if (this.balance  amt){            this.balance - amt;            target.balance  amt;          }        }      }    } finally {      actr.free(this, target)    }  }} 2. 破坏不可抢占条件 破坏不可抢占条件看上去很简单核心是要能够主动释放它占有的资源这一点synchronized是做不到的。原因是synchronized申请资源的时候如果申请不到线程直接进入阻塞状态了而线程进入阻塞状态啥都干不了也释放不了线程已经占有的资源。 你可能会质疑“Java作为排行榜第一的语言这都解决不了”你的怀疑很有道理Java在语言层次确实没有解决这个问题不过在SDK层面还是解决了的java.util.concurrent这个包下面提供的Lock是可以轻松解决这个问题的。 3. 破坏循环等待条件 破坏这个条件需要对资源进行排序然后按序申请资源。这个实现非常简单我们假设每个账户都有不同的属性 id这个 id 可以作为排序字段申请的时候我们可以按照从小到大的顺序来申请。比如下面代码中①~⑥处的代码对转出账户this和转入账户target排序然后按照序号从小到大的顺序锁定账户。这样就不存在“循环”等待了。 class Account {  private int id;  private int balance;  // 转账  void transfer(Account target, int amt){    Account left  this        ①    Account right  target;    ②    if (this.id  target.id) { ③      left  target;           ④      right  this;            ⑤    }                          ⑥    // 锁定序号小的账户    synchronized(left){      // 锁定序号大的账户      synchronized(right){        if (this.balance  amt){          this.balance - amt;          target.balance  amt;        }      }    }  }} 总结 互斥锁在并发领域的知名度极高只要有了并发问题大家首先容易想到的就是加锁因为大家都知道加锁能够保证执行临界区代码的互斥性。这样理解虽然正确但是却不能够指导你真正用好互斥锁。临界区的代码是操作受保护资源的路径类似于球场的入口入口一定要检票也就是要加锁但不是随便一把锁都能有效。所以必须深入分析锁定的对象和受保护资源的关系综合考虑受保护资源的访问路径多方面考量才能用好互斥锁。 synchronized是Java在语言层面提供的互斥原语其实Java里面还有很多其他类型的锁但作为互斥锁原理都是相通的锁一定有一个要锁定的对象至于这个锁定的对象要保护的资源以及在哪里加锁/解锁就属于设计层面的事情了。 对如何保护多个资源关键是要分析多个资源之间的关系。如果资源之间没有关系很好处理每个资源一把锁就可以了。如果资源之间有关联关系就要选择一个粒度更大的锁这个锁应该能够覆盖所有相关的资源。除此之外还要梳理出有哪些访问路径所有的访问路径都要设置合适的锁这个过程可以类比一下门票管理。 我们再引申一下上面提到的关联关系关联关系如果用更具体、更专业的语言来描述的话其实是一种“原子性”特征在前面我们提到的原子性主要是面向CPU指令的转账操作的原子性则是属于是面向高级语言的不过它们本质上是一样的。 “原子性”的本质 是什么其实不是不可分割不可分割只是外在表现其本质是多个资源间有一致性的要求 操作的中间状态对外不可见。例如在32位的机器上写long型变量有中间状态只写了64位中的32位在银行转账的操作中也有中间状态账户A减少了100账户B还没来得及发生变化。所以 解决原子性问题是要保证中间状态对外不可见。 最后我们还讲了 用细粒度锁来锁定多个资源时要注意死锁的问题。这个就需要你能把它强化为一个思维定势遇到这种场景马上想到可能存在死锁问题。当你知道风险之后才有机会谈如何预防和避免因此 识别出风险很重要。 预防死锁主要是破坏三个条件中的一个有了这个思路后实现就简单了。但仍需注意的是有时候预防死锁成本也是很高的。例如上面转账那个例子我们破坏占用且等待条件的成本就比破坏循环等待条件的成本高破坏占用且等待条件我们也是锁了所有的账户而且还是用了死循环 while(!actr.apply(this, target)); 方法不过好在apply()这个方法基本不耗时。 在转账这个例子中破坏循环等待条件就是成本最低的一个方案。 所以我们在选择具体方案的时候还需要 评估一下操作成本从中选择一个成本最低的方案。 如果本文对你有帮助的话欢迎点赞分享这对我继续分享创作优质文章非常重要。感谢 本文由 mdnice 多平台发布
http://www.dnsts.com.cn/news/230945.html

相关文章:

  • 如何建网站快捷方式做相册的网站
  • 东莞建设网站开发珠海专业做网站制作
  • 视频分享网站建设难吗南昌市建设局网站
  • 如何建设社交网站水土保持生态建设网站
  • 网络公司做机场网站网站群建设方案6
  • 乾安网站建设哪家好工地用的木模板是什么板
  • c 网站开发简单实例wordpress网站慢
  • 建设网站需要分析什么条件黄南州wap网站建设公司
  • 多层分销网站建设wordpress模版sns
  • 最新企业网站开发和设计软件网站制作加教程视频
  • 软件学校网站模板教师网络培训心得体会
  • 设计师一般用什么网站个人网站构建
  • 做棋牌网站多少钱做网站申请域名大概花费多少
  • 响应式网站价格网络营销推广方法和应用场景
  • 网站专题设计软件网站建设参考文献资料
  • 学校网站建设评比广西网络电视
  • 书法网站优化关键词led网站建设方案模板
  • 淘宝导航里的链接网站怎么做微网站免费模板
  • 建设网站需要花费多少钱百度搜不到公司网站
  • 台州网站制作定制食品类网站模板
  • 组成原理毕业设计代做网站推荐移动互联网技术网站
  • 网站建设搜索经典网站首页
  • 网站中页面链接怎么做的建设银行登录网站
  • 建设银行梅李分行网站郑州现在可以正常出入吗
  • 机构网站建设需要交费吗什么网站可以找到防水工程做
  • 网站建设吉金手指排名15目前网页设计工资多少
  • 做网站和视频剪辑用曲面屏国外引流推广平台
  • 红安县建设局网站百度seo搜索引擎优化
  • 网站做美工常州自助建站seo
  • 品牌厂家网站建设app store下载官方