本文源码:GitHub·点这里 || GitEE·点这里

一、资源和加锁

1、场景描述

多线程并发访问同一个资源问题,假如线程A获取变量之后修改变量值,线程C在此时也获取变量值并且修改,两个线程同时并发处理一个变量,就会导致并发问题。

这种并行处理数据库的情况在实际的业务开发中很常见,两个线程先后修改数据库的值,导致数据有问题,该问题复现的概率不大,处理的时候需要对整个模块体系有概念,才能容易定位问题。

2、演示案例

public class LockThread01 {public static void main(String[] args) {CountAdd countAdd = new CountAdd() ;AddThread01 addThread01 = new AddThread01(countAdd) ;addThread01.start();AddThread02 varThread02 = new AddThread02(countAdd) ;varThread02.start();}
}
class AddThread01 extends Thread {private CountAdd countAdd  ;public AddThread01 (CountAdd countAdd){this.countAdd = countAdd ;}@Overridepublic void run() {countAdd.countAdd(30);}
}
class AddThread02 extends Thread {private CountAdd countAdd  ;public AddThread02 (CountAdd countAdd){this.countAdd = countAdd ;}@Overridepublic void run() {countAdd.countAdd(10);}
}
class CountAdd {private Integer count = 0 ;public void countAdd (Integer num){try {if (num == 30){count = count + 50 ;Thread.sleep(3000);} else {count = count + num ;}System.out.println("num="+num+";count="+count);} catch (Exception e){e.printStackTrace();}}
}

这里案例演示多线程并发修改count值,导致和预期不一致的结果,这是多线程并发下最常见的问题,尤其是在并发更新数据时。

出现并发的情况时,就需要通过一定的方式或策略来控制在并发情况下数据读写的准确性,这被称为并发控制,实现并发控制手段也很多,最常见的方式是资源加锁,还有一种简单的实现策略:修改数据前读取数据,修改的时候加入限制条件,保证修改的内容在此期间没有被修改。

二、锁的概念简介

1、锁机制简介

并发编程中一个最关键的问题,多线程并发处理同一个资源,防止资源使用的冲突一个关键解决方法,就是在资源上加锁:多线程序列化访问。锁是用来控制多个线程访问共享资源的方式,锁机制能够让共享资源在任意给定时刻只有一个线程任务访问,实现线程任务的同步互斥,这是最理想但性能最差的方式,共享读锁的机制允许多任务并发访问资源。

2、悲观锁

悲观锁,总是假设每次每次被读取的数据会被修改,所以要给读取的数据加锁,具有强烈的资源独占和排他特性,在整个数据处理过程中,将数据处于锁定状态,例如synchronized关键字的实现就是悲观机制。

悲观锁的实现,往往依靠数据库提供的锁机制,只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据,悲观锁主要分为共享读锁和排他写锁。

排他锁基本机制:又称写锁,允许获取排他锁的事务更新数据,阻止其他事务取得相同的资源的共享读锁和排他锁。若事务T对数据对象A加上写锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的写锁。

3、乐观锁

乐观锁相对悲观锁而言,采用更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务的开销非常的占资源,乐观锁机制在一定程度上解决了这个问题。

乐观锁大多是基于数据版本记录机制实现,为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个version字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号等于数据库表当前版本号,则予以更新,否则认为是过期数据。乐观锁机制在高并发场景下,可能会导致大量更新失败的操作。

乐观锁的实现是策略层面的实现:CAS(Compare-And-Swap)。当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能成功更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

4、机制对比

悲观锁本身的实现机制就以损失性能为代价,多线程争抢,加锁、释放锁会导致比较多的上下文切换和调度延时,加锁的机制会产生额外的开销,还有增加产生死锁的概率,引发性能问题。

乐观锁虽然会基于对比检测的手段判断更新的数据是否有变化,但是不确定数据是否变化完成,例如线程1读取的数据是A1,但是线程2操作A1的值变化为A2,然后再次变化为A1,这样线程1的任务是没有感知的。

悲观锁每一次数据修改都要上锁,效率低,写数据失败的概率比较低,比较适合用在写多读少场景。

乐观锁并未真正加锁,效率高,写数据失败的概率比较高,容易发生业务形异常,比较适合用在读多写少场景。

是选择牺牲性能,还是追求效率,要根据业务场景判断,这种选择需要依赖经验判断,不过随着技术迭代,数据库的效率提升,集群模式的出现,性能和效率还是可以两全的。

三、Lock基础案例

1、Lock方法说明

lock:执行一次获取锁,获取后立即返回;

lockInterruptibly:在获取锁的过程中可以中断;

tryLock:尝试非阻塞获取锁,可以设置超时时间,如果获取成功返回true,有利于线程的状态监控;

unlock:释放锁,清理线程状态;

newCondition:获取等待通知组件,和当前锁绑定;

2、应用案例

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockThread02 {public static void main(String[] args) {LockNum lockNum = new LockNum() ;LockThread lockThread1 = new LockThread(lockNum,"TH1");LockThread lockThread2 = new LockThread(lockNum,"TH2");LockThread lockThread3 = new LockThread(lockNum,"TH3");lockThread1.start();lockThread2.start();lockThread3.start();}
}
class LockNum {private Lock lock = new ReentrantLock() ;public void getNum (){lock.lock();try {for (int i = 0 ; i < 3 ; i++){System.out.println("ThreadName:"+Thread.currentThread().getName()+";i="+i);}} finally {lock.unlock();}}
}
class LockThread extends Thread {private LockNum lockNum ;public LockThread (LockNum lockNum,String name){this.lockNum = lockNum ;super.setName(name);}@Overridepublic void run() {lockNum.getNum();}
}

这里多线程基于Lock锁机制,分别依次执行任务,这是Lock的基础用法,各种API的详解,下次再说。

3、与synchronized对比

基于synchronized实现的锁机制,安全性很高,但是一旦线程失败,直接抛出异常,没有清理线程状态的机会。显式的使用Lock语法,可以在finally语句中最终释放锁,维护相对正常的线程状态,在获取锁的过程中,可以尝试获取,或者尝试获取锁一段时间。

四、源代码地址

GitHub·地址
https://github.com/cicadasmile/java-base-parent
GitEE·地址
https://gitee.com/cicadasmile/java-base-parent

推荐阅读:Java基础系列

序号 文章标题
A01 Java基础:基本数据类型,核心点整理
A02 Java基础:特殊的String类,和相关扩展API
B01 Java并发:线程的创建方式,状态周期管理
B02 Java并发:线程核心机制,基础概念扩展
B03 Java并发:多线程并发访问,同步控制
B04 Java并发:线程间通信,等待/通知机制

Java并发编程(05):悲观锁和乐观锁机制相关推荐

  1. JAVA并发编程:悲观锁与乐观锁

    生活 晴. 悲观与乐观的情绪概念 本篇来了解一下悲观锁和乐观锁,在了解这两个锁之前,我们首先有必要把悲观和乐观这两个词搞清楚: 悲观:对世事怀有消极的看法,认为事物总往糟糕的方向发展. 乐观:对世事怀 ...

  2. Java 并发编程必须知道的七种锁类型以及应用

    锁是解决并发冲突的重要工具.在开发中我们会用到很多类型的锁,每种锁都有其自身的特点和适用范围. 需要深刻理解锁的理念和区别,才能正确.合理地使用锁. 常用锁类型 乐观锁与悲观锁 悲观锁对并发冲突持悲观 ...

  3. Java并发编程—Synchronized底层优化(偏向锁、轻量级锁)

    原文作者:Matrix海 子 原文地址:Java并发编程:Synchronized底层优化(偏向锁.轻量级锁) 目录 一.重量级锁 二.轻量级锁 三.偏向锁 四.其他优化 五.总结 一.重量级锁 上篇 ...

  4. 读Java并发编程实践记录_原子性_锁_同步容器详解_任务执行

    原子性: 单独的,不可分割的操作 不要使用过期状态值来决策当下的状态, 一定要先检查再执行(不检查, 将引发数据修改,丢失) 避免延迟初始化(懒加载: 先查看对象 == null, 然后new), 有 ...

  5. 教你“强人锁男”——java并发编程的常用锁类型

    Java 并发编程不可不知的七种锁类型与注意事项 锁是java并发编程中最重要的同步机制.锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息.锁是解决并发冲突的重要工具.在开发 ...

  6. 学习笔记:Java 并发编程④_无锁

    若文章内容或图片失效,请留言反馈. 部分素材来自网络,若不小心影响到您的利益,请联系博主删除. 视频链接:https://www.bilibili.com/video/av81461839 配套资料: ...

  7. Java并发编程—常见面试题

    建议: 学习java并发前需要先掌握JVM知识 关于下面问题档案的详细解析都在后面推荐的相关系列文章中 一.线程安全相关 1.什么叫线程安全? 线程安全就是说多线程访问同一代码,不会产生不确定的结果. ...

  8. 学习笔记:Java 并发编程①_基础知识入门

    若文章内容或图片失效,请留言反馈. 部分素材来自网络,若不小心影响到您的利益,请联系博主删除. 视频链接:https://www.bilibili.com/video/av81461839 视频下载: ...

  9. 学习笔记:Java 并发编程②_管程

    若文章内容或图片失效,请留言反馈. 部分素材来自网络,若不小心影响到您的利益,请联系博主删除. 视频链接:https://www.bilibili.com/video/av81461839 配套资料: ...

最新文章

  1. Java分布式 RPC 框架性能大比拼,Dubbo真的最差吗?
  2. 十年沉淀、厚积薄发,百度CTO王海峰秀出NLP十年成果
  3. Windows Phone7天初学(4):数据绑定
  4. php基础教学笔记,php学习笔记:基础知识
  5. 招博士生 | 澳门科技大学人工智能课题组
  6. 打造 AI 语音新标杆,英特尔与腾讯云小微创新共赢
  7. 【逆元】HDU-1576
  8. 众多时间时钟Flash动画素材一键即可获取
  9. 数据分析实战项目:SQL分析淘宝用户行为
  10. vue 开发App监听手机 返回键返回上级路由以及退出
  11. 360浏览器下载|360安全浏览器下载
  12. 谈谈Android中的persistent属性
  13. 各种音视频编解码学习详解(7)--微软Windows Media系列
  14. java svg pdf_Java 插入SVG到PDF文档
  15. [MIT]微积分重点 第三课 极值和二阶导数 学习笔记
  16. npm 同时安装同一个包的不同版本(npm@6.9 alias)
  17. Android使用Webview无法弹框问题,及解决Error: Java exception was raised during method invocation
  18. Dichotomy专栏:Leetcode:#33 搜索旋转排序数组
  19. 什么软件可以搜python题_Python答题神器下载-Python百万答题软件 _5577安卓网
  20. 报表软件选型时应该知道的

热门文章

  1. mysql php宝塔 root_宝塔面板,脚本命令
  2. python整数分节输出_pyfactor
  3. 3-5:HTTP协议之Cookie和Session
  4. LeetCode 417 太平洋大西洋水流问题
  5. golang strconv包(类型转换、保留小数位)
  6. python多进程管道通信(精)
  7. 双系统windows10扩容ubuntu16.04
  8. 行为型模式:迭代器模式
  9. 案例49-crm练习获取客户列表带有分页和筛选功能
  10. 20171213-python自动化-接口测试-jmeter-get-设置header