小米科技:Java并发编程:悲观锁和乐观锁机制


小米科技:Java并发编程:悲观锁和乐观锁机制
文章图片
小米科技:Java并发编程:悲观锁和乐观锁机制
一、资源和加锁多线程并发访问同一个资源问题 , 假如线程A获取变量之后修改变量值 , 线程C在此时也获取变量值并且修改 , 两个线程同时并发处理一个变量 , 就会导致并发问题 。
这种并行处理数据库的情况在实际的业务开发中很常见 , 两个线程先后修改数据库的值 , 导致数据有问题 , 该问题复现的概率不大 , 处理的时候需要对整个模块体系有概念 , 才能容易定位问题 。
出现并发的情况时 , 就需要通过一定的方式或策略来控制在并发情况下数据读写的准确性 , 这被称为并发控制 , 实现并发控制手段也很多 , 最常见的方式是资源加锁 , 还有一种简单的实现策略:修改数据前读取数据 , 修改的时候加入限制条件 , 保证修改的内容在此期间没有被修改 。
二、锁的概念简介1、锁机制简介
并发编程中一个最关键的问题 , 多线程并发处理同一个资源 , 防止资源使用的冲突一个关键解决方法 , 就是在资源上加锁:多线程序列化访问 。 锁是用来控制多个线程访问共享资源的方式 , 锁机制能够让共享资源在任意给定时刻只有一个线程任务访问 , 实现线程任务的同步互斥 , 这是最理想但性能最差的方式 , 共享读锁的机制允许多任务并发访问资源 。
2、悲观锁
悲观锁 , 总是假设每次每次被读取的数据会被修改 , 所以要给读取的数据加锁 , 具有强烈的资源独占和排他特性 , 在整个数据处理过程中 , 将数据处于锁定状态 , 例如synchronized关键字的实现就是悲观机制 。
悲观锁的实现 , 往往依靠数据库提供的锁机制 , 只有数据库层提供的锁机制才能真正保证数据访问的排他性 , 否则 , 即使在本系统中实现了加锁机制 , 也无法保证外部系统不会修改数据 , 悲观锁主要分为共享读锁和排他写锁 。
排他锁基本机制:又称写锁 , 允许获取排他锁的事务更新数据 , 阻止其他事务取得相同的资源的共享读锁和排他锁 。 若事务T对数据对象A加上写锁 , 事务T可以读A也可以修改A , 其他事务不能再对A加任何锁 , 直到T释放A上的写锁 。
3、乐观锁
乐观锁相对悲观锁而言 , 采用更加宽松的加锁机制 。 悲观锁大多数情况下依靠数据库的锁机制实现 , 以保证操作最大程度的独占性 。 但随之而来的就是数据库性能的大量开销 , 特别是对长事务的开销非常的占资源 , 乐观锁机制在一定程度上解决了这个问题 。
乐观锁大多是基于数据版本记录机制实现 , 为数据增加一个版本标识 , 在基于数据库表的版本解决方案中 , 一般是通过为数据库表增加一个version字段来实现 。 读取出数据时 , 将此版本号一同读出 , 之后更新时 , 对此版本号加一 。 此时 , 将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对 , 如果提交的数据版本号等于数据库表当前版本号 , 则予以更新 , 否则认为是过期数据 。 乐观锁机制在高并发场景下 , 可能会导致大量更新失败的操作 。
乐观锁的实现是策略层面的实现:CAS(Compare-And-Swap) 。 当多个线程尝试使用CAS同时更新同一个变量时 , 只有其中一个线程能成功更新变量的值 , 而其它线程都失败 , 失败的线程并不会被挂起 , 而是被告知这次竞争中失败 , 并可以再次尝试 。
4、机制对比
悲观锁本身的实现机制就以损失性能为代价 , 多线程争抢 , 加锁、释放锁会导致比较多的上下文切换和调度延时 , 加锁的机制会产生额外的开销 , 还有增加产生死锁的概率 , 引发性能问题 。