求教,多线程脏读问题!

魏飞翔 2020-11-09 10:27:39
问题描述:
多线程获取内容(消费kafka数据),想实现的功能是数据满1000条或者30秒则把数据推送到下游接口
问题:
查看数据库中数据发现会造成丢数、脏读等情况。

...全文
2211 20 打赏 收藏 转发到动态 举报
写回复
用AI写文章
20 条回复
切换为时间正序
请发表友善的回复…
发表回复
魏飞翔 2020-11-11
  • 打赏
  • 举报
回复
我的解决方法是把kafka改为批量消费啦,所以上面问题也就是不存在,谢谢大家
魏飞翔 2020-11-11
  • 打赏
  • 举报
回复
引用 16 楼 冰思雨 的回复:
entityMap的用法有些问题,synchronized关键字的用法也存在瑕疵。 1. 先说一下entityMap,楼主对 ConcurrentHashMap 的理解还是存在问题,这个Map是线程安全的类,只能保证它的单独一次调用是线程安全的,也就是说它的锁区间只包含在单个的方法调用期间。 楼主在使用 entityMap 的过程中,是先调用了 containsKey 函数,然后才是 get 或者 put 方法,这种用法的逻辑思路很清晰,但是,你忽略了一点,就是它的锁不是上在if块上的,只在单独的 containsKey 函数调用期间有锁保护(单独的get/put调用也一样),所以,代码的保护区间出现了问题。造成的后果是:相同batchKey的两个线程,一个线程调用完 containsKey 返回false之后被切换出去,另一个线程也调用 containsKey 函数,也会返回 false,因为第一个线程的if块不是整体锁保护的,在执行完containsKey函数之后,是可以被切换出去的。然后,两个线程都会执行false条件下的代码块,创建了两个ArrayList,当然,先创建的ArrayList会被后创建的覆盖掉,从而造成数据的丢失。 2. List<CouponEntity> list 的用法也存在问题。多线程状况下,没有对核心数据操作进行锁保护,一个线程执行完 sendPostRequest 函数后别切换出去,另一个线程已然可以对相同的list进行sendPostRequest调用,因为前一个线程没有调用clear方法,list里面仍然有数据,从而造成数据的重复发送。 3. 为啥最后说这个 synchronized 呢? 因为,exeKafka 方法上的同步关键字,它的锁对象不是 entityMap ,所以,后面那个在 entityMap上面使用同步关键字的代码,失去了意义。两个不同的锁对象,代表两个独立的锁保护机制。正确的做法应该是对同一个对象进行上锁,然后,保护这个对象的多个数据操作代码,从而达到同一时刻只能有一个线程进入被保护的其中一个代码块,达到线程安全的目的。 额外解释一下:为啥 exeKafka 函数已经上锁了,为啥还会出现数据问题呢? 关键就在于你只保证了同一时刻只有一个线程在调用exeKafka函数,但是,目前的synchronized关键字的用法,你无法保证一个线程在执行exeKafka函数时,其他线程不能调用timerSend函数啊。如果有两个线程同时分别调用exeKafka函数和timerSend函数,会产生什么结果? 看看我1和2说的内容,你品,你细品~
我明白i的意思,这块代码已经优化啦。还是谢谢大佬给讲解的多线程这块知识
幽饮烛 2020-11-11
  • 打赏
  • 举报
回复
都用 kafka 了,下游直接消费处理就行了。
冰思雨 2020-11-11
  • 打赏
  • 举报
回复
最后吐槽一下楼主的这段代码吧。 1. 对线程安全以及synchronized的理解有严重的偏差,代码写成这个样子,面试线程安全相关的问题,肯定是过不去的。 2. 对线程安全的类以及使用方法缺少使用经验,感觉线程安全方面的编程应该是个空白的样子。 实际上,滥用synchronized关键字,会造成代码的执行效率极其低下,最极端的情况是不如单个线程的执行效率高。 编写多线程程序的时候,还要注意,很多线程安全的类都会提供一些原子操作的函数,完成一些由一个逻辑判断加一个数据访问组成的操作,巧用这些函数可以免去很多if判断然后put/get这样的用法,从而避免由锁保护区不完整造成线程安全的问题。总之,不是说使用了线程安全的类对象,就不会产生线程安全的问题了,关键还是要会用才行。
冰思雨 2020-11-11
  • 打赏
  • 举报
回复
entityMap的用法有些问题,synchronized关键字的用法也存在瑕疵。 1. 先说一下entityMap,楼主对 ConcurrentHashMap 的理解还是存在问题,这个Map是线程安全的类,只能保证它的单独一次调用是线程安全的,也就是说它的锁区间只包含在单个的方法调用期间。 楼主在使用 entityMap 的过程中,是先调用了 containsKey 函数,然后才是 get 或者 put 方法,这种用法的逻辑思路很清晰,但是,你忽略了一点,就是它的锁不是上在if块上的,只在单独的 containsKey 函数调用期间有锁保护(单独的get/put调用也一样),所以,代码的保护区间出现了问题。造成的后果是:相同batchKey的两个线程,一个线程调用完 containsKey 返回false之后被切换出去,另一个线程也调用 containsKey 函数,也会返回 false,因为第一个线程的if块不是整体锁保护的,在执行完containsKey函数之后,是可以被切换出去的。然后,两个线程都会执行false条件下的代码块,创建了两个ArrayList,当然,先创建的ArrayList会被后创建的覆盖掉,从而造成数据的丢失。 2. List<CouponEntity> list 的用法也存在问题。多线程状况下,没有对核心数据操作进行锁保护,一个线程执行完 sendPostRequest 函数后别切换出去,另一个线程已然可以对相同的list进行sendPostRequest调用,因为前一个线程没有调用clear方法,list里面仍然有数据,从而造成数据的重复发送。 3. 为啥最后说这个 synchronized 呢? 因为,exeKafka 方法上的同步关键字,它的锁对象不是 entityMap ,所以,后面那个在 entityMap上面使用同步关键字的代码,失去了意义。两个不同的锁对象,代表两个独立的锁保护机制。正确的做法应该是对同一个对象进行上锁,然后,保护这个对象的多个数据操作代码,从而达到同一时刻只能有一个线程进入被保护的其中一个代码块,达到线程安全的目的。 额外解释一下:为啥 exeKafka 函数已经上锁了,为啥还会出现数据问题呢? 关键就在于你只保证了同一时刻只有一个线程在调用exeKafka函数,但是,目前的synchronized关键字的用法,你无法保证一个线程在执行exeKafka函数时,其他线程不能调用timerSend函数啊。如果有两个线程同时分别调用exeKafka函数和timerSend函数,会产生什么结果? 看看我1和2说的内容,你品,你细品~
863922230 2020-11-10
  • 打赏
  • 举报
回复
集群的话就要使用分布式锁,你使用synchronized肯定是不行的。
魏飞翔 2020-11-10
  • 打赏
  • 举报
回复
引用 4 楼 aaaa00 的回复:
你是服务器是集群还是单机,要是集群的话就是加synchronized 也是不行的,建议使用redis锁,或者zk锁,还有你@scheduled的定时任务的里面的hashmap 最好换为ConcurrentHashamp试试
集群,kafka3个partition对应我这边3个节点,跟集群没多大关系吧。我在我本地模拟测试,也会有这种情况,本质还是hashmap这块的读写有问题吧
魏飞翔 2020-11-10
  • 打赏
  • 举报
回复
引用 2 楼 qybao 的回复:
你是怎么用synchronized的?在用到entityMap的地方加synchronized(entityMap)获得锁后再使用entityMap 另外,你的entity是在哪里取得的?取得的条件是什么,有没有可能重复取得数据?
863922230 2020-11-10
  • 打赏
  • 举报
回复
你是服务器是集群还是单机,要是集群的话就是加synchronized 也是不行的,建议使用redis锁,或者zk锁,还有你@scheduled的定时任务的里面的hashmap 最好换为ConcurrentHashamp试试
魏飞翔 2020-11-10
  • 打赏
  • 举报
回复
原本代码是这样的
  • 打赏
  • 举报
回复
是满1000条,插入一次数据库吗,写库的代码有问题吧,可以贴出来看看?
魏飞翔 2020-11-10
  • 打赏
  • 举报
回复
引用 13 楼 KeepSayingNo 的回复:
你搞个临时表,接收到的数据直接插表。再搞个job扫描临时表,取1000条发,发完了删除或者标记删除。JOB 30秒执行一次,不就行了吗
就是不想每次都操作数据库,才全局变量,弄成批量的
qybao 2020-11-10
  • 打赏
  • 举报
回复
你是怎么用synchronized的?在用到entityMap的地方加synchronized(entityMap)获得锁后再使用entityMap 另外,你的entity是在哪里取得的?取得的条件是什么,有没有可能重复取得数据?
KeepSayingNo 2020-11-10
  • 打赏
  • 举报
回复
你搞个临时表,接收到的数据直接插表。再搞个job扫描临时表,取1000条发,发完了删除或者标记删除。JOB 30秒执行一次,不就行了吗
苏颙 2020-11-10
  • 打赏
  • 举报
回复
引用 10 楼 卖萌吐槽 的回复:
引用 9 楼 苏颙 的回复:
这代码写的 槽多无口,直接取出来数据不足 1000 直接扔到调度线程池,大于 1000 直接发送就完事了,整个全局变量肯定有问题啊
不用全局变量,kafka推过来的数据,如何知道条数?
你从 kafka poll 出来数据了 肯定知道条数呀
魏飞翔 2020-11-10
  • 打赏
  • 举报
回复
引用 8 楼 a975719898 的回复:
全局容器没问题,关键你的任务调度逻辑有些不清楚 1000&30s 这两个条件以先到的为准 执行发送下游数据 过后开始下一个运作周期是吧? 推荐使用 juc下 condition 完成这段逻辑 比你这清晰多了
是这样的,监听kafka,然后不停的消费kafka数据,数据满1000&30s就推给下游
魏飞翔 2020-11-10
  • 打赏
  • 举报
回复
引用 9 楼 苏颙 的回复:
这代码写的 槽多无口,直接取出来数据不足 1000 直接扔到调度线程池,大于 1000 直接发送就完事了,整个全局变量肯定有问题啊
不用全局变量,kafka推过来的数据,如何知道条数?
苏颙 2020-11-10
  • 打赏
  • 举报
回复
这代码写的 槽多无口,直接取出来数据不足 1000 直接扔到调度线程池,大于 1000 直接发送就完事了,整个全局变量肯定有问题啊
阿豆响当当 2020-11-10
  • 打赏
  • 举报
回复
全局容器没问题,关键你的任务调度逻辑有些不清楚 1000&30s 这两个条件以先到的为准 执行发送下游数据 过后开始下一个运作周期是吧? 推荐使用 juc下 condition 完成这段逻辑 比你这清晰多了
魏飞翔 2020-11-09
  • 打赏
  • 举报
回复
加synchronized,或者使用hashtable也试了,问题依然存在

67,512

社区成员

发帖
与我相关
我的任务
社区描述
J2EE只是Java企业应用。我们需要一个跨J2SE/WEB/EJB的微容器,保护我们的业务核心组件(中间件),以延续它的生命力,而不是依赖J2SE/J2EE版本。
社区管理员
  • Java EE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧