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并发这么大的"系统",需要自己好好总结.整理才能征服它.希望同仁们一起来学习 ...
随机推荐
- Android 编译系统
1,Makefile编译方式 TARGET: PREREQUISITES COMMANDS 1,TARGET是需要生成的目标文件,PREREQUISTIES代表了目标所依赖的所有文件. 2,简单的Ma ...
- 字符串 kmp算法 codeforce 625B 题解(模板)
题解:kmp算法 代码: #include <iostream>#include <algorithm>#include <cstring>#include < ...
- flask之CORS跨域请求处理
from flask import Flask from flask_cors import CORS#pip install Flask-CORS#跨域请求模块 app = Flask(__name ...
- HTTP请求格式
HTTP请求格式 URL包含:/index/index2?a=1&b=2:路径和参数都在这里. 请求头部: · content-length表示请求体里面的数据长度: · ...
- MYSQL 中binlog 参数的记录
http://dev.mysql.com/doc/refman/5.7/en/replication-options-binary-log.html binlog_cache_size Command ...
- elasticsearch7.X x-pack破解
简介: x-pack是elasticsearch的一个收费的扩展包,将权限管理,警告,监视等功能捆绑在一个易于安装的软件包中,x-pack被设计为一个无缝的工作,但是你可以轻松的启用或者关闭一些功能. ...
- 405 - 不允许用于访问此页的 HTTP 谓词的处理办法
今天介绍的是针对访问html页面时出现此类错误的处理办法,如果你的问题页面是其他类型,可以参考如下信息: IIS 返回 405 - 不允许用于访问此页的 HTTP 谓词.终极解决办法!!!! 1.为什 ...
- SpringCloud(二)- Consul介绍、安装、使用
唯能极于情,故能极于剑有问题或错误请及时联系小编或关注小编公众号 “CodeCow”,小编一定及时回复和改正,期待和大家一起学习交流 此文由四部分组成(Consul简介.安装.实操.总结),别着急,慢 ...
- 0521Day03命名规范 Data函数 可变长参数 枚举类型
[重点] 命名规范 枚举类型 Date函数 可变长参数 pirnt,println 命名规范 1. 驼峰命名法:main,username,setUsername 用于变量.方法的命名 2. Pasc ...
- [JavaWeb基础] 021.Action中result的各种转发类型
在struts2中, struts.xml中result的类型有多种,它们类似于struts1中的forward,常用的类型有dispatcher(默认值).redirect.redirectActi ...