前提

首先该场景是一个酒店开房的业务。为了朋友们阅读简单,我把业务都简化了。

业务:开房后会添加一条账单,添加一条房间排期记录,房间排期主要是为了房间使用的时间不冲突。如:账单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) {
// 判断房间排期是否有冲突...
}
}
  1. sychronized(RoomSchedule.class),相当于的开房业务都是串行的。不管开房间1还是房间2。都需要等待上一个线程执行完开房业务,后续才能执行。这并不好哦。
  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并发编程实战总结 (一)的更多相关文章

  1. 【Java并发编程实战】----- AQS(四):CLH同步队列

    在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形.其主要从两方面进行了改造:节点的结构与节点等待机制.在结构上引入了头 ...

  2. 【Java并发编程实战】----- AQS(三):阻塞、唤醒:LockSupport

    在上篇博客([Java并发编程实战]----- AQS(二):获取锁.释放锁)中提到,当一个线程加入到CLH队列中时,如果不是头节点是需要判断该节点是否需要挂起:在释放锁后,需要唤醒该线程的继任节点 ...

  3. 【Java并发编程实战】----- AQS(二):获取锁、释放锁

    上篇博客稍微介绍了一下AQS,下面我们来关注下AQS的所获取和锁释放. AQS锁获取 AQS包含如下几个方法: acquire(int arg):以独占模式获取对象,忽略中断. acquireInte ...

  4. 【Java并发编程实战】-----“J.U.C”:CountDownlatch

    上篇博文([Java并发编程实战]-----"J.U.C":CyclicBarrier)LZ介绍了CyclicBarrier.CyclicBarrier所描述的是"允许一 ...

  5. 【Java并发编程实战】-----“J.U.C”:CyclicBarrier

    在上篇博客([Java并发编程实战]-----"J.U.C":Semaphore)中,LZ介绍了Semaphore,下面LZ介绍CyclicBarrier.在JDK API中是这么 ...

  6. 【Java并发编程实战】-----“J.U.C”:ReentrantReadWriteLock

    ReentrantLock实现了标准的互斥操作,也就是说在某一时刻只有有一个线程持有锁.ReentrantLock采用这种独占的保守锁直接,在一定程度上减低了吞吐量.在这种情况下任何的"读/ ...

  7. 【Java并发编程实战】-----“J.U.C”:Semaphore

    信号量Semaphore是一个控制访问多个共享资源的计数器,它本质上是一个"共享锁". Java并发提供了两种加锁模式:共享锁和独占锁.前面LZ介绍的ReentrantLock就是 ...

  8. 【Java并发编程实战】-----“J.U.C”:ReentrantLock之三unlock方法分析

    前篇博客LZ已经分析了ReentrantLock的lock()实现过程,我们了解到lock实现机制有公平锁和非公平锁,两者的主要区别在于公平锁要按照CLH队列等待获取锁,而非公平锁无视CLH队列直接获 ...

  9. 【Java并发编程实战】-----“J.U.C”:ReentrantLock之一简介

    注:由于要介绍ReentrantLock的东西太多了,免得各位客官看累,所以分三篇博客来阐述.本篇博客介绍ReentrantLock基本内容,后两篇博客从源码级别分别阐述ReentrantLock的l ...

  10. 【java并发编程实战】-----线程基本概念

    学习Java并发已经有一个多月了,感觉有些东西学习一会儿了就会忘记,做了一些笔记但是不系统,对于Java并发这么大的"系统",需要自己好好总结.整理才能征服它.希望同仁们一起来学习 ...

随机推荐

  1. HTTP Status完整枚举

    HTTP Status完整枚举 public enum HttpStatus { // 1xx Informational    1xx信息类的 继续    CONTINUE(100, "C ...

  2. 站在CSS3的肩上定义选择器

    按上下文选择元素 按祖先元素选择要格式化的元素 输入ancestor,这里的ancestor是希望格式化的元素的祖先元素的选择器. 输入一个空格(必不可少). 如果需要,对后续的每个祖先元素重复第(1 ...

  3. router-link传参

    果然还好是一小部分一小部分记录的好. <router-link :to="info">中:to特性可以是路径str,也可以是一个对象形式str. 当info是对象时可以 ...

  4. RenderBox使用说明书&原理浅析

    本文基于1.12.13+hotfix.8版本源码分析. 0.目录 一.RenderBox的用法 1.RenderBox的使用基本流程 2.RenderObjectWidget 3.非容器控件的hitT ...

  5. CSS类与选择器【转】http://www.cnblogs.com/duanhuajian/archive/2012/12/17/2821524.html

    1.在 HTML 中,一个 class 值中可能包含一个词列表,各个词之间用空格分隔.例如,如果希望将一个特定的元素同时标记为重要(important)和警告(warning),就可以写作(这两个词的 ...

  6. Mybatis 强大的结果集映射器resultMap

    1. 前言 resultMap 元素是 MyBatis 中最重要最强大的元素.它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC ...

  7. leetcode350之实现求解两数组交集(包含重复元素)

    给定两个数组,编写一个函数来计算它们的交集. 说明: 输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致. 我们可以不考虑输出结果的顺序 def binarySearch(nums, t ...

  8. Android_存储之文件存储

    前面几篇随笔 讲到的关于存储的,SharedPreferences.Room.数据库等 最终都是以文件形式 存储到手机上的(除特殊的存储于手机内存的:如Room可以创建内存数据库). 这些存储方式,A ...

  9. 初窥 BB-Framework

     

  10. [安卓基础] 008.Android中的显示单位

    *:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...