Java并发编程实战总结 (一)
前提
首先该场景是一个酒店开房的业务。为了朋友们阅读简单,我把业务都简化了。
业务:开房后会添加一条账单,添加一条房间排期记录,房间排期主要是为了房间使用的时间不冲突。如:账单A,使用房间1,使用时间段为2020-06-01 12:00 - 2020-06-02 12:00 ,那么还需要使用房间1开房的时间段则不能与账单A的时间段冲突。
业务类
为了简单起见,我把几个实体类都简化了。
账单类
public class Bill {
// 账单号
private String serial;
// 房间排期id
private Integer room_schedule_id;
// ...get set
}
房间类
// 房间类
public class Room {
private Integer id;
// 房间名
private String name;
// get set...
}
房间排期类
import java.sql.Timestamp;
public class RoomSchedule {
private Integer id;
// 房间id
private Integer roomId;
// 开始时间
private Timestamp startTime;
// 结束时间
private Timestamp endTime;
// ...get set
}
实战
并发实战当然少不了Jmeter压测工具,传送门: https://jmeter.apache.org/download_jmeter.cgi
为了避免有些小伙伴访问不到官网,我上传到了百度云:链接:https://pan.baidu.com/s/1c9l3Ri0KzkdIkef8qtKZeA
提取码:kjh6
初次实战(sychronized)
第一次进行并发实战,我是首先想到sychronized
关键字的。没办法,基础差。代码如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import java.sql.Timestamp;
/**
* 开房业务类
*/
@Service
public class OpenRoomService {
@Autowired
DataSourceTransactionManager dataSourceTransactionManager;
@Autowired
TransactionDefinition transactionDefinition;
public void openRoom(Integer roomId, Timestamp startTime, Timestamp endTime) {
// 开启事务
TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
try {
synchronized (RoomSchedule.class) {
if (isConflict(roomId, startTime, endTime)) {
// throw exception
}
// 添加房间排期...
// 添加账单
// 提交事务
dataSourceTransactionManager.commit(transaction);
}
} catch (Exception e) {
// 回滚事务
dataSourceTransactionManager.rollback(transaction);
throw e;
}
}
public boolean isConflict(Integer roomId, Timestamp startTime, Timestamp endTime) {
// 判断房间排期是否有冲突...
}
}
sychronized(RoomSchedule.class)
,相当于的开房业务都是串行的。不管开房间1还是房间2。都需要等待上一个线程执行完开房业务,后续才能执行。这并不好哦。- 事务必须在同步代码块
sychronized
中提交,这是必须的。否则当线程A使用房间1开房,同步代码块执行完,事务还未提交,线程B发现房间1的房间排期没有冲突,那么此时是有问题的。
错误点: 有些朋友可能会想到都是串行执行了,为什么不把synchronized
关键字写到方法上?
首先openRoom
方法是非静态方法,那么synchronized
锁定的就是this
对象。而Spring中的@Service
注解类是多例的,所以并不能把synchronized
关键字添加到方法上。
二次改进(等待-通知机制)
因为上面的例子当中,开房操作都是串行的。而实际情况使用房间1开房和房间2开房应该是可以并行才对。如果我们使用synchronized(Room实例)
可以吗?答案是不行的。
在第三章 解决原子性问题当中,我讲到了使用锁必须是不可变对象,若把可变对象作为锁,当可变对象被修改时相当于换锁,这里的锁讲的就是synchronized
锁定的对象,也就是Room实例。因为Room实例是可变对象(set方法修改实例的属性值,说明为可变对象),所以不能使用synchronized(Room实例)
。
在这次改进当中,我使用了第五章 等待-通知机制,我添加了RoomAllocator
房间资源分配器,当开房的时候需要在RoomAllocator
当中获取锁资源,获取失败则线程进入wait()
等待状态。当线程释放锁资源则notiryAll()
唤醒所有等待中的线程。
RoomAllocator
房间资源分配器代码如下:
import java.util.ArrayList;
import java.util.List;
/**
* 房间资源分配器(单例类)
*/
public class RoomAllocator {
private final static RoomAllocator instance = new RoomAllocator();
private final List<Integer> lock = new ArrayList<>();
private RoomAllocator() {}
/**
* 获取锁资源
*/
public synchronized void lock(Integer roomId) throws InterruptedException {
// 是否有线程已占用该房间资源
while (lock.contains(roomId)) {
// 线程等待
wait();
}
lock.add(roomId);
}
/**
* 释放锁资源
*/
public synchronized void unlock(Integer roomId) {
lock.remove(roomId);
// 唤醒所有线程
notifyAll();
}
public static RoomAllocator getInstance() {
return instance;
}
}
开房业务只需要修改openRoom的方法,修改如下:
public void openRoom(Integer roomId, Timestamp startTime, Timestamp endTime) throws InterruptedException {
RoomAllocator roomAllocator = RoomAllocator.getInstance();
// 开启事务
TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
try {
roomAllocator.lock(roomId);
if (isConflict(roomId, startTime, endTime)) {
// throw exception
}
// 添加房间排期...
// 添加账单
// 提交事务
dataSourceTransactionManager.commit(transaction);
} catch (Exception e) {
// 回滚事务
dataSourceTransactionManager.rollback(transaction);
throw e;
} finally {
roomAllocator.unlock(roomId);
}
}
那么此次修改后,使用房间1开房和房间2开房就可以并行执行了。
总结
上面的例子可能会有其他更好的方法去解决,但是我的实力不允许我这么做....。这个例子也是我自己在项目中搞事情搞出来的。毕竟没有实战经验,只有理论,不足以学好并发。希望大家也可以在项目中搞事情[坏笑],当然不能瞎搞。
后续如果在其他场景用到了并发,也会继续写并发实战的文章哦~
个人博客网址: https://colablog.cn/
如果我的文章帮助到您,可以关注我的微信公众号,第一时间分享文章给您
Java并发编程实战总结 (一)的更多相关文章
- 【Java并发编程实战】----- AQS(四):CLH同步队列
在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形.其主要从两方面进行了改造:节点的结构与节点等待机制.在结构上引入了头 ...
- 【Java并发编程实战】----- AQS(三):阻塞、唤醒:LockSupport
在上篇博客([Java并发编程实战]----- AQS(二):获取锁.释放锁)中提到,当一个线程加入到CLH队列中时,如果不是头节点是需要判断该节点是否需要挂起:在释放锁后,需要唤醒该线程的继任节点 ...
- 【Java并发编程实战】----- AQS(二):获取锁、释放锁
上篇博客稍微介绍了一下AQS,下面我们来关注下AQS的所获取和锁释放. AQS锁获取 AQS包含如下几个方法: acquire(int arg):以独占模式获取对象,忽略中断. acquireInte ...
- 【Java并发编程实战】-----“J.U.C”:CountDownlatch
上篇博文([Java并发编程实战]-----"J.U.C":CyclicBarrier)LZ介绍了CyclicBarrier.CyclicBarrier所描述的是"允许一 ...
- 【Java并发编程实战】-----“J.U.C”:CyclicBarrier
在上篇博客([Java并发编程实战]-----"J.U.C":Semaphore)中,LZ介绍了Semaphore,下面LZ介绍CyclicBarrier.在JDK API中是这么 ...
- 【Java并发编程实战】-----“J.U.C”:ReentrantReadWriteLock
ReentrantLock实现了标准的互斥操作,也就是说在某一时刻只有有一个线程持有锁.ReentrantLock采用这种独占的保守锁直接,在一定程度上减低了吞吐量.在这种情况下任何的"读/ ...
- 【Java并发编程实战】-----“J.U.C”:Semaphore
信号量Semaphore是一个控制访问多个共享资源的计数器,它本质上是一个"共享锁". Java并发提供了两种加锁模式:共享锁和独占锁.前面LZ介绍的ReentrantLock就是 ...
- 【Java并发编程实战】-----“J.U.C”:ReentrantLock之三unlock方法分析
前篇博客LZ已经分析了ReentrantLock的lock()实现过程,我们了解到lock实现机制有公平锁和非公平锁,两者的主要区别在于公平锁要按照CLH队列等待获取锁,而非公平锁无视CLH队列直接获 ...
- 【Java并发编程实战】-----“J.U.C”:ReentrantLock之一简介
注:由于要介绍ReentrantLock的东西太多了,免得各位客官看累,所以分三篇博客来阐述.本篇博客介绍ReentrantLock基本内容,后两篇博客从源码级别分别阐述ReentrantLock的l ...
- 【java并发编程实战】-----线程基本概念
学习Java并发已经有一个多月了,感觉有些东西学习一会儿了就会忘记,做了一些笔记但是不系统,对于Java并发这么大的"系统",需要自己好好总结.整理才能征服它.希望同仁们一起来学习 ...
随机推荐
- php反序列化(昨天的补充)
魔术方法 在对PHP反序列化进行利用时,经常需要通过反序列化中的魔术方法,检查方法里是否有敏感操作来进行利用. 常见方法: 创建对象时触发:__construct() 对象被销毁时触发:__destr ...
- es6中class类的全方面理解
传统的javascript中只有对象,没有类的概念.它是基于原型的面向对象语言.原型对象特点就是将自身的属性共享给新对象.这样的写法相对于其它传统面向对象语言来讲,很有一种独树一帜的感脚!非常容易让人 ...
- pyppteer下
目录 启动pyppteer 切图 获取响应头,响应头状态,cookies 获取当前页面标题 获取页面html 第一种:获取整个页面html 第二种:只获取文本 注入JS,控制上下滚动 选择器 获取元素 ...
- Tensorflow从0到1(一)之如何安装Tensorflow(Windows和Linux两种版本)
现在越来越多的人工智能和机器学习以及深度学习,强化学习出现了,然后自己也对这个产生了点兴趣,特别的进行了一点点学习,就通过这篇文章来简单介绍一下,关于如何搭建Tensorflow以及如何进行使用.建议 ...
- java web基础
WEB基础 C/S:即服务器-客服机(Client-Server)结构.C/S结构通常采用两层结构,服务器负责数据的管理,客户机负责完成与用户的交互任务.客户通过局域网与服务器相连,接受用户的请求,并 ...
- 在ORACLE中实现SELECT TOP N的方法----[转]
1.在ORACLE中实现SELECT TOP N 由于ORACLE不支持SELECT TOP语句,所以在ORACLE中经常是用ORDER BY跟ROWNUM的组合来实现SELECT TOP N的查询. ...
- Vue踩坑日记
1.错误:找不到模块'eslint-config-standard' https://github.com/standard/eslint-config-standard/issues/84 我遇到了 ...
- 环境篇:Zeppelin
环境篇:Zeppelin Zeppelin 是什么 Apache Zeppelin 是一个让交互式数据分析变得可行的基于网页的开源框架.Zeppelin提供了数据分析.数据可视化等功能. Zeppel ...
- 前端Json对象与Json字符串互转(4种转换方式)
1>jQuery插件支持的转换方式: $.parseJSON( jsonstr ); //jQuery.parseJSON(jsonstr),可以将json字符串转换成json对象 2>浏 ...
- Git-Jenkins-代码的上线
第一章:自动化上线代码基本介绍 1.软件开发生命周期 老板的创意---产品经理---立项---开发团队---测试团队---运维上线 产品经理---加需求---开发团队---测试----更新代码,上线 ...