前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Java并发编程实战总结 (一)

Java并发编程实战总结 (一)

作者头像
Johnson木木
发布于 2020-06-07 03:00:11
发布于 2020-06-07 03:00:11
50900
代码可运行
举报
文章被收录于专栏:猿小俊猿小俊
运行总次数:0
代码可运行

前提

首先该场景是一个酒店开房的业务。为了朋友们阅读简单,我把业务都简化了。 业务:开房后会添加一条账单,添加一条房间排期记录,房间排期主要是为了房间使用的时间不冲突。如:账单A,使用房间1,使用时间段为2020-06-01 12:00 - 2020-06-02 12:00 ,那么还需要使用房间1开房的时间段则不能与账单A的时间段冲突。

业务类

为了简单起见,我把几个实体类都简化了。

账单类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Bill {
    // 账单号
    private String serial;

    // 房间排期id
    private Integer room_schedule_id;
    // ...get set
}

房间类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 房间类
public class Room {
    private Integer id;

    // 房间名
    private String name;
    // get set...
}

房间排期类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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关键字的。没办法,基础差。代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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房间资源分配器代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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的方法,修改如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    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/

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-06-06 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
《java并发编程实战》总结
①发挥多处理器的强大优势 ②建模的简单性 ③异步事件的简化处理④相应更灵敏的用户界面
CBeann
2023/12/25
2430
《java并发编程实战》总结
Java并发编程实战(八)
守护线程(Daemon Thread)是JVM中的一种特殊线程,它的主要作用是监控JVM的状态,当JVM中的其他线程结束时,会自动退出。而本地线程(Native Thread)是指在应用程序中创建的线程,它们是由操作系统分配和管理的。
疯狂的KK
2023/04/25
3170
Java并发编程实战(八)
java并发编程实战wwj———————-第一阶段————–27-28-29-30
sleep:是Thread的方法,sleep不释放锁,sleep不用synchronized,不需要被唤醒。
全栈程序员站长
2022/11/10
2050
java并发编程实战wwj———————-第一阶段————–27-28-29-30
【二十七】springboot实现多线程事务处理
在前面二十四章做了一个springboot使用EasyExcel和线程池实现多线程导入Excel数据的demo,在写时忘了做事务处理,评论区有个大佬提出来了,这章就对二十四章的代码做一个改造,完善多线程的事务处理。 对于springboot的事务处理,前面在二十三章也做过springboot整合spring事务详解以及实战的学习,但是在多线程时,这个东西并不适用,本章就通过手写事务处理(编程式事务处理)。 由于本章是针对二十四章的批量导入功能的扩展,所有不会再写事务处理不相关的(二十四章的内容)介绍了。
小z666
2024/06/21
5220
【二十七】springboot实现多线程事务处理
java并发编程实战(3) Lock显示锁
  synchronized是java中的一个关键字,也就是说是Java语言内置的特性。那么为什么会出现Lock呢?
黄规速
2022/04/14
3780
java并发编程实战(3)  Lock显示锁
Java并发编程实战系列10之避免活跃性危险
10.1 死锁 哲学家问题 有环 A等B,B等A 数据库往往可以检测和解决死锁//TODO JVM不行,一旦死锁只有停止重启。 下面分别介绍了几种典型的死锁情况: 10.1.1 Lock ordering Deadlocks 下面是一个经典的锁顺序死锁:两个线程用不同的顺序来获得相同的锁,如果按照锁的请求顺序来请求锁,就不会发生这种循环依赖的情况。 public class LeftRightDeadlock { private final Object left = new Object();
JavaEdge
2018/04/28
7030
Java并发编程原理: 线程之间的互斥与协作机制
可能在synchronized关键字的实现原理中,你已经知道了它的底层是使用Monitor的相关指令来实现的,但是还不清楚Monitor的具体细节。本文将让你彻底Monitor的底层实现原理。
一个会写诗的程序员
2020/07/10
6480
Java并发编程实战 05等待-通知机制和活跃性问题
Java并发编程实战 01并发编程的Bug源头 Java并发编程实战 02Java如何解决可见性和有序性问题 Java并发编程实战 03互斥锁 解决原子性问题 Java并发编程实战 04死锁了怎么办
Johnson木木
2020/05/21
4860
Java并发编程实战 03互斥锁 解决原子性问题
Java并发编程实战 01并发编程的Bug源头 Java并发编程实战 02Java如何解决可见性和有序性问题
Johnson木木
2020/05/08
5450
Java并发编程实战 03互斥锁 解决原子性问题
Java并发编程-各种锁
来 源:https://www.cnblogs.com/huangjuncong/p/8542008.html
JavaFish
2019/10/16
8710
java并发编程(三)
为什么我锁了String的所有实例化对象,对Myobject没有加锁,他仍会互斥执行呢?
疯狂的KK
2020/02/26
2960
java并发编程(十一)
38、volatile 的作用是保证多线程环境下的可见性和禁止指令重排。应用场景包括:
疯狂的KK
2023/04/25
1720
java并发编程(十一)
java并发编程(七)
线程池是一种管理和复用线程的机制,它可以避免频繁创建和销毁线程的开销,提高程序的性能和稳定性。Java提供了Executor框架来支持线程池的实现,常用的实现类有ThreadPoolExecutor和ScheduledThreadPoolExecutor。
疯狂的KK
2023/03/21
3370
Java并发编程系列-(1) 并发编程基础
一般来讲,CPU核心数和线程数的关系为核心数:线程数=1:1;但是如果使用了超线程技术,可以达到1:2甚至更多。
码老思
2023/10/17
2800
Java并发编程系列-(1) 并发编程基础
Java并发编程:synchronized
Java并发编程:synchronized   虽然多线程编程极大地提高了效率,但是也会带来一定的隐患。比如说两个线程同时往一个数据库表中插入不重复的数据,就可能会导致数据库中插入了相同的数据。今天我们就来一起讨论下线程安全问题,以及Java中提供了什么机制来解决线程安全问题。   以下是本文的目录大纲:   一.什么时候会出现线程安全问题?   二.如何解决线程安全问题?   三.synchronized同步方法或者同步块   若有不正之处,请多多谅解并欢迎批评指正。   请尊重作者劳动成果,转载请标明原
陈树义
2018/04/13
8810
Java并发编程:synchronized
Java并发编程详解:实现高效并发应用的关键技术
除了上述提到的线程安全性、锁、并发集合和原子操作,Java还提供了一些强大的并发工具,帮助开发者更好地实现高效的并发编程。
小尘要自信
2023/10/10
3520
Spring5学习笔记Spring事务处理
我们系统的每个业务方法可能包括了多个原子性的数据库操作,比如下面的 save() 方法中就有两个原子性的数据库操作。这些原子性的数据库操作是有依赖的,它们要么都执行,要不就都不执行。
程序员Leo
2023/09/09
2070
Spring5学习笔记Spring事务处理
java并发编程实战(5) happen-before原则和彻底明白DCL的volatile作用。
public class Singleton { private static Singleton singleton;
黄规速
2022/04/14
4710
java并发编程实战(5) happen-before原则和彻底明白DCL的volatile作用。
Java并发编程的总结和思考
编写优质的并发代码是一件难度极高的事情。Java语言从第一版本开始内置了对多线程的支持,这一点在当年是非常了不起的,但是当我们对并发编程有了更深刻的认识和更多的实践后,实现并发编程就有了更多的方案和更好的选择。本文是对并发编程的一点总结和思考,同时也分享了Java 5以后的版本中如何编写并发代码的一点点经验。
大龄老码农-昊然
2021/04/03
3030
Java并发编程的总结和思考
redis 分布式锁的 5个坑,真是又大又深
最近项目上线的频率颇高,连着几天加班熬夜,身体有点吃不消精神也有些萎靡,无奈业务方催的紧,工期就在眼前只能硬着头皮上了。脑子浑浑噩噩的时候,写的就不能叫代码,可以直接叫做Bug。我就熬夜写了一个bug被骂惨了。
程序员小富
2020/04/22
2.3K0
推荐阅读
相关推荐
《java并发编程实战》总结
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文