精华内容
下载资源
问答
  •  这个问题解决之后又发现了一个新问题,Redis在多线程高并发下出现数据错乱,也就是A的数据给了B,B的数据给到了C.... 分析:程序是多线程并且高并发情况,而Redis是单线程,也就是程序在Redis驱动返回结果时发生了...

             公司某个项目使用了redis进行数据的快速写入与查询,但在测试中发现它的查询速度完全不敌MySQL.经过代码分析发现,我们的架构师来了个奇葩操作.Redis与MySQL的关联,也就是先从Redis中查出相关数据在循环查询MySQL来获取完整数据,对于这种操作只能认为架构的脑袋被电梯夹了,在没有什么借口可以解释.好多人都是这样,对新技术有点皮毛了解就想"大显身手",结果就闹得很是尴尬.

           大家知道Redis是非关系型数据库,特点就是"快",那我们直接把想要的数据全部存入Redis中,不做任何关联.

           这个问题解决之后又发现了一个新问题,Redis在多线程高并发下出现数据错乱,也就是A的数据给了B,B的数据给到了C....

    分析:程序是多线程并且高并发情况,而Redis是单线程,也就是程序在Redis驱动返回结果时发生了张冠李戴的现象.

    解决方法:Redis操作方法添加线程锁(lock),让其他线程排队.

    展开全文
  • 多线程高并发编程】二 实现多线程的几种方式

    万次阅读 多人点赞 2020-02-17 23:32:54
    本文我们来看看多线程的应用场景,为什么要用多线程,以及实现一个多线程有几种方式。

    程序猿学社的GitHub,欢迎Star
    https://github.com/ITfqyd/cxyxs
    本文已记录到github,形成对应专题

    前言:

    上章,我们已经了解线程的一些基本概念。本文我们来看看多线程的应用场景,为什么要用多线程,以及实现一个多线程有几种方式。

    1.什么是多线程?

    多线程是指通过软件优化和硬件(CPU)的方式,同时并发运行多个线程(任务)。更好的运行系统的资源。

    例如,社长,很久以前,接到boss的提的一个业务,需要开发一个充电桩管理物联网管理平台,实现通过网站,查看各个充电桩的情况。如果就社长一个人开发,感觉1年搞定都有点难,毕竟社长专注于后端开发,这时社长就跟boss提出,需要增加人马,招一个前端,一个后端。社长就负责跟硬件对接,每个人负责一块,各种同步开发。最后,通过社长三人的努力,半年就交差了。这就是多线程的好处。多个人,信息也是共享(一个进程内的多个线程,资源是共享在同一个内存中)
    在这里插入图片描述

    1.1应用场景

    • 网站发送多个请求,会一一返回结果,也就是很高的使用了多线程。如果没有多线程,我们抢票,就得发一个请求后,需要等请求处理完后,才能运行。
    • 扣扣聊天界面,如果没有多线程,发一个消息,需要上一个消息处理完后,才能处理下一个需求。
    • 通过netty解析数据报文,如果没有多线程,1w个线程,直接怼进来,我们只能一个个处理,肯定处理不过来,如果没有多线程,解析逻辑也无法和业务逻辑分离开,实现程序的解耦。

    2.实现一个多线程的常见几种方式

    为了模拟真实的场景,每个线程中,都增加了延迟运行的代码。

    Thread.sleep(1000);
    

    这句代码表示休眠1秒钟,以毫秒为单位。

    通过继承的方式,实现多线程(第一种)

    package com.cxyxs.two;
    
    import java.util.Date;
    
    /**
     * Description:第一种:通过继承的方式,实现多线程
     *  转发请注明来源  程序猿学社 - https://ithub.blog.csdn.net/
     * Author: 程序猿学社
     * Date:  2020/2/17 21:37
     * Modified By:
     */
    public class MyThreadExtend extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("程序猿学社:社长在开发中,通过继承的方式实现:" + new Date());
            }
        }
    }
    

    调用代码

      //第一种方式
      MyThreadExtend threadExtend = new MyThreadExtend();
      threadExtend.start();
    
    • 因为java中是单继承,所以不推荐通过这种方式实现多线程。如果该类已经被继承,是无法继承Thread类的。

    通过实现runnable接口,实现多线程(第二种)

    package com.cxyxs.two;
    
    import java.util.Date;
    
    /**
     * Description:转发请注明来源  程序猿学社 - https://ithub.blog.csdn.net/
     * Author: 程序猿学社
     * Date:  2020/2/17 21:43
     * Modified By:
     */
    public class MyThreadRunnable implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("小二在开发中,通过实现Runnable接口方式实现:" + new Date());
            }
        }
    }
    

    调用代码

    //第二种方式
    MyThreadRunnable runnable = new MyThreadRunnable();
    Thread thread = new Thread(runnable);
    thread.start();
    

    匿名内部类实现(第三种)

    Runnable方式

    //匿名内部类-第一种
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("通过匿名内部类的方式第一种实现!");
                }
            }).start();
    

    继承类方式

    //匿名内部类-第二种
            new Thread(){
                @Override
                public void run() {
                    System.out.println("通过匿名内部类的方式第二种实现!");
                }
            }.start();
    

    通过Callable实现多线程(第四种)

    public class MyThreadCallable implements Callable<Integer> {
    
        @Override
        public Integer call() throws Exception {
            int sum=0;
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                sum+=i;
                System.out.println("小王通过实现Callable接口的方式实现:" + new Date());
            }
            return sum;
    
        }
    }
    

    调用代码

    //第四种 通过Callable实现多线程
            MyThreadCallable callable = new MyThreadCallable();
            FutureTask<Integer> result = new FutureTask<Integer>(callable);
            new Thread(result).start();
            try {
                Integer sum =  result.get();
                System.out.println("计算结果:"+sum);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
    
    • 之前几种实现多线程的方法,需要重写run方法,启动多线程。而Callable方式实现多线程,需要重写call方法。通过FutureTask包装器来创建Thread线程
    • 通过调用get方法获取多线程的运行结果。注意,get方法会一直堵塞,没有返回值,主线程会一直等待。
    • 应用场景,例如批量大数据的导出,假设我们要导出100w数据,需要30s,我们就可以通过分页,每个线程查10w的数据,启动10个线程,来获取处理结果。这样就可以通过多线程提供查询的效率。

    启动一个main线程,启动了几个线程

     public class ThreadCount {
        public static void main(String[] args) {
            ThreadGroup group =
                    Thread.currentThread().getThreadGroup();
            int count = group.activeCount();
            group.list();
        }
    }
    

    idea打印
    在这里插入图片描述
    eclipse打印
    在这里插入图片描述
    启动了2个线程,一个主线程main,是程序的入口。
    还有一个[Monitor Ctrl-Break,这是IDEA特有的监控线程。正确的打印应该是gc线程。通过上面两张图,就可以得出这个结论。

    多个线程进行测试

    public class Test {
        public static void main(String[] args) {
            //第一种方式
            MyThreadExtend threadExtend = new MyThreadExtend();
            threadExtend.start();
    
            //第二种方式
            MyThreadRunnable runnable = new MyThreadRunnable();
            Thread thread = new Thread(runnable);
            thread.start();
    
            //第三种方式
            //匿名内部类-第一种
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("通过匿名内部类的方式第一种实现!");
                }
            }).start();
    
            //匿名内部类-第二种
            new Thread() {
                @Override
                public void run() {
                    System.out.println("通过匿名内部类的方式第二种实现!");
                }
            }.start();
    
            //第四种 通过Callable实现多线程
            MyThreadCallable callable = new MyThreadCallable();
            FutureTask<Integer> result = new FutureTask<Integer>(callable);
            new Thread(result).start();
            try {
                Integer sum =  result.get();
                System.out.println("计算结果:"+sum);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
    

    在这里插入图片描述

    • 通过测试的结果,我们发现,数据是交换运行的,如果是单cpu,每次只能运行一个通道,就算用多线程的方式实现,每次也是交替运行。只是计算机运行速度很快,我们看不出什么区别。说到这里,就有社友提出疑问,单cpu,还有必要用多线程吗
      就算是单cpu,利用多线程也有很多好处。让我们可以减少没必要的等待,更好的利用资源。
    • 如果是多cpu,就可以同时并发运行多个任务,大大的提高运行效率。
    展开全文
  • :故而此文将介绍一下分布式锁出现的背景以及如何才能将分布式锁搬上用场(即如何重新多线程高并发的场景)。 实战内容 : 1、“同一时刻多个线程高并发下访问共享资源”的场景在当前互联网产品或者项目下并不...

    实战前言:上篇博文我总体介绍了我这套视频课程:“SpringBoot实战实现分布式锁” 总体涉及的内容,从本篇文章开始,我将开始介绍其中涉及到的相关知识要点,感兴趣的小伙伴可以关注关注学习学习!!工欲善其事,必先利其器,介绍分布式锁使用的前因后果之前,得先想办法说清楚为啥需要分布式锁以及如何才需要将分布式锁搬上用场!!
    其中,该课程的学习链接:https://edu.csdn.net/course/detail/9986

    实战概要:故而此文将介绍一下分布式锁出现的背景以及如何才能将分布式锁搬上用场(即如何重新多线程高并发的场景)。

    实战内容
    1、“同一时刻多个线程高并发下访问共享资源”的场景在当前互联网产品或者项目下并不少见,这一场景随之带来的问题便显而易见:这一共享资源在并发访问的前后出现了数据不一致或者并非预期出现的结果的现象!!简而言之,这种现象其实就是大伙熟悉的 “高并发多线程访问共享资源时需要加同步代码块”的口头语(甚至可以说是面试时常见的对白了!)

    2、单体应用时代加“同步锁”常见的方式是利用jdk天然提供的类/组件:ReentrantLock或者Synchronized,但在分布式系统架构下项目一般以微服务的方式开发、独立部署甚至集群部署,当不同的服务或者集群环境同一服务不同实例发生对共享资源的高并发访问时,ReentrantLock或者Synchronized 的方式将很难解决 “高并发导致数据不一致或者并发预期出现的结果”的问题!!

    3、于是乎,“分布式锁”便出现了,“分布式锁”其实只是一解决方案,并非一专有组件或者类,实现这一解决方案仍旧需要借助额外的组件或者中间件来辅助,甚至某些情况下,需要借助数据库级别的方式来实现。总体来说,目前较为流行的解决方式还是有很多种,在我的视频课程或者文章中,我将介绍一下几种方式来实战实现 “分布式锁”
    (1)数据库级别锁-乐观悲观锁
    (2)基于Redis的原子操作实现分布式锁
    (3)基于Zookeeper实战实现分布式锁
    (4)基于Redisson实战实现分布式锁

    4、既然我们知道分布式锁出现的背景以及其相应的实战实现方式,那我们回到本篇文章的核心内容:重现多线程高并发访问共享资源的场景

    5、下面我们以“商城系统/秒杀系统抢单”场景为例,借助Jmeter测试工具,基于SpringBoot微服务项目重现高并发多线程访问共享资源的场景!即:重现1秒内100线程、1000线程、10000线程等充当抢单请求对一商品进行抢单!!!

    6、这一场景其实很像“抢微信红包”、“某一商城如小米商城饥饿营销时抢手机”等业务场景。下面我们大概模拟重现其中的核心逻辑-即抢单的过程:建库-spring_boot_distribute,建一商品信息表语句如下(mysql5.6版本):

    CREATE TABLE `product_lock` (  
    `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
    `product_no` varchar(255) DEFAULT NULL COMMENT '产品编号',
    `stock` int(11) DEFAULT NULL COMMENT '库存量',
    `version` int(11) DEFAULT NULL COMMENT '版本号',
    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
    `update_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    PRIMARY KEY (`id`),
    UNIQUE KEY `idx_unique` (`id`) USING BTREE
    ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='产品信息表';
    

    并在其中录入一个商品的信息(主要是库存 stock 的设置)!
    enter image description here

    7、接着采用IDEA的SpringBoot Initializr组件构建多模块的SpringBoot微服务项目,并开发一Controller跟一Service,采用Mybatis逆向工程生成上面那张数据库表对应的entity、mapper、mapper.xml,相关代码以及截图如下:

    @Service
    public class DataLockService {
    
    private static final Logger log= LoggerFactory.getLogger(DataLockService.class);
    
    @Autowired
    private ProductLockMapper lockMapper;
    
    /**
     * 正常更新商品库存 - 重现了高并发的场景
     * @param dto
     * @return
     * @throws Exception
     */
    @Transactional(rollbackFor = Exception.class)
    public int updateStock(ProductLockDto dto) throws Exception{
        int res=0;
    
        ProductLock entity=lockMapper.selectByPrimaryKey(dto.getId());
        if (entity!=null && entity.getStock().compareTo(dto.getStock())>=0){
            entity.setStock(dto.getStock());
            return lockMapper.updateStock(entity);
        }
    
        return res;
    }}
    

    DataLockController代码如下:

    @RestController
    public class DataLockController {
    
    private static final Logger log= LoggerFactory.getLogger(DataLockController.class);
    

    private static final String prefix=“lock”;

    @Autowired
    private DataLockService dataLockService;
    
    /**
     * 更新商品库存-1
     * @param dto
     * @param bindingResult
     * @return
     */
    @RequestMapping(value = prefix+"/data/base/positive/update",method = RequestMethod.POST,consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public BaseResponse dataBasePositive(@RequestBody @Validated ProductLockDto dto, BindingResult bindingResult){
        if (bindingResult.hasErrors()){
            return new BaseResponse(StatusCode.InvalidParam);
        }
    
        BaseResponse response=new BaseResponse(StatusCode.Ok);
        try {
            log.debug("当前请求数据:{} ",dto);
    
            int res=dataLockService.updateStock(dto);
            if (res<=0) {
                return new BaseResponse(StatusCode.Fail);
            }
        }catch (Exception e){
            log.error("发生异常:",e.fillInStackTrace());
            response=new BaseResponse(StatusCode.Fail);
        }
        return response;
    }}
    

    ProductLockDto 代码如下:

    @Data
    @ToString
    public class ProductLockDto {
    @NotNull
    private Integer id;
    
    @NotNull
    private Integer stock=1;}
    

    Mybatis逆向工程生成的那三个组件就不贴了,在这里贴一下 DataLockService调用的ProductLockMapper更新库存的方法以及动态sql的写法:

    ProductLockMapper类的方法: int updateStock(ProductLock lock);

    ProductLockMapper.xml的动态sql:

    <!--更新库存-->
    <update id="updateStock" parameterType="com.debug.steadyjack.entity.ProductLock">
    update product_lock
    set stock = stock - #{stock,jdbcType=INTEGER}
    where id = #{id,jdbcType=INTEGER}
    </update>
    

    8、至此简单的抢单系统/商城秒杀系统的抢单场景就大致模拟好了,下面我们采用Jmeter测试工具来模拟这一高并发场景,Jmeter的相关设置如下:
    (1)首先我们设置1s并发100个线程,后面你可以在这里设置1000、10000甚至更多个线程!
    enter image description here

    (2)接着我们设置 “HTTP信息头管理器” ,因为我们的抢单接口接收的媒体类型是 json格式的post请求!
    enter image description here

    (3)接着我们创建 “HTTP请求” ,设置我们的项目上下文、端口以及我们的请求接口路径跟方法体(ProductLockDto的字段:商品的id跟需要抢的量stock)
    enter image description here

    (4)最后我们设置stock字段来源于我们配置的CSV数据文件设置中读取的变量stock 的值,即代表我们的用户可以任意随机的下单一定的量!!
    enter image description here

    (5)其中的csv文件是长这样的:
    enter image description here

    9、最后,我们点击这一按钮,即开启了 1s 内启动100个并发线程对设定的产品进行 “抢” 的请求。
    enter image description here

    10、这个时候,我们先对这一产品的库存量在数据库进行设置,我们设置为 100,即现有的库存量为100。理论情况下,不管发生多少次的“哄抢”,“最终的库存应当是被抢完而且应当是恰好被抢完,而且需要发送相应的短信/通知告知用户抢到了!!”,然后,现实是很残酷的(当你按下那一个start run的按钮时,数据库最终出现的结果却不是我们预期的那样!!)

    11、下面是抢单接口的打印日志以及数据库最终对这一商品更新的结果:
    enter image description here

    enter image description here

    15、你会惊讶的看到,100个库存在随机产生的100个线程(每个线程库存2或者5-csv文件读取的)更新之后竟然变成了负数(按道理来说,我们写的数据库更新逻辑以及代码判断逻辑没有多大问题啊!!!)

    实战分析:“按道理来说,我们写的数据库更新逻辑以及代码判断逻辑没有多大问题啊!!!”,实则不然,其实问题正是出在这两点:数据库更新逻辑 跟 代码判断逻辑 。 欲知问题何在,请听下回分解!!

    实战总结:本篇文章主要基于SpringBoot微服务项目重现了高并发多线程并发访问同一共享资源时出现的问题,学习过程大伙若有相关问题可以加我QQ:1948831260 进行技术交流!若需要该课程的学习,亦可以加QQ进行咨询!如果感兴趣的童鞋,也可以关注关注我的公众号哦!!技术交流群:605610429(Java实战基地交流1群)

    展开全文
  • 多线程高并发编程】一进程和线程(并发和并行)

    千次阅读 多人点赞 2020-02-15 19:45:35
    了解并发和并行,进程和线程的一些概念,更好的学习多线程编程

    程序猿学社的GitHub,欢迎Star:
    https://github.com/ITfqyd/cxyxs
    觉得有用,可以点赞,关注,评论,留言四连发。

    前言

    为了更好的学习多线程,在这里我们了解一下,一些概念性的描述,只有理解这些概念后,才能更好的学习多线程。加油!!!

    1.并发和并行

    并发

    并发指在同一时刻只能有一条指令执行。也就是单个cpu。一般是只多个线程进行交互,达到理论上的同时执行效果,实际上是交替执行,。但是因为,但是因为cpu处理速度过快,我们几乎很难察觉。
    例如:一般写着博客,到了吃饭的点,再去吃饭,一直重复,中间有间隙,同一时刻,只能做一件事。举例子的时候,我们只说把间隙放大了而已。

    并行

    并发指同一时刻,同时运行多个任务。可以我们已经鸟枪换大炮了,通过多个cpu,还并行处理多个任务。
    例如:我要开发一个博客管理系统,一个人开发要一个月。boss觉得太慢了,想了想,这效率太慢了,正好,我有钱,一口气,招了3个人。这个项目。10多天就完成勒。通过多个人同时开发,一个做用户,一个做论坛,一个做文章管理,同时进行。大大提高了开发效率。

    2. 什么是进程和线程

    进程

    进程是cpu资源分配的最小单位,进程之间不能共享资源。进程有自己独立的地址空间。一个进程可能包含多个线程。

    为了方便理解,我们通过一些案例更好的理解一下。
    进程就是我们上网过程中,启动某个应用,他就是进程。
    例如:我们开发的项目,发布到服务器下,就会启动一个预知对应的进程。
    下图是社长启动的所有进程。可以通过ctrl+alt+delet组合键查看。
    在这里插入图片描述

    线程

    线程是cpu调度的最小单位,线程还有自己的栈和栈指针,程序计数器等寄存器。线程必须依赖于进程。可以理解为进程是父亲。线程是儿子,一个父亲可能有多个儿子,每个儿子,肯定有一个父亲,当然,如果是孙悟空,就另说。

    点击某个网站,实际上就会调用一个线程。假设用的是tomcat作为服务端,我们访问网站,向后台发送一个请求,会调用一个http-bio-端口号的线程。
    启动一个微信,我们发现我们能看到的有4个线程。这里的微信也就是我们所说的进程。也就是说启动一个微信应用进程。会启动4个线程。
    在这里插入图片描述

    后记

    程序猿学社的GitHub,欢迎Star:
    https://github.com/ITfqyd/cxyxs
    觉得有用,可以点赞,关注,评论,留言四连发。

    展开全文
  • 多线程高并发

    千次阅读 2017-11-28 20:19:56
    高并发想让服务器(tomcat)能接受处理用户请求。   二、高并发例子 举个极端的例子,就是100个人,1人分配1台web服务器, 那么服务器资源是他们独占的,他们不需要抢占服务器资源,100个请求被100台服务器...
  • JAVA多线程高并发学习

    千次阅读 2019-06-14 16:40:27
    工欲善其事,必先利其器 ...定义:当线程访问某个类时,不管采用任何调度方式,不需要额外的同步或者协调,这个类都能表现出正确的结果,这个类就成为是线程安全的 线程安全性主要体现在 原子性: 互斥访问,同一时...
  • 多线程高并发编程】五 线程的生命周期

    千次阅读 多人点赞 2020-02-22 19:00:00
    本文通过分析Thread里面的State枚举类的源码,结合demo,更好的理解线程的生命周期
  • 并发编程的三大概念:原子性、有序性、可见性。这篇文章保你看完就懂 我们知道volatile可以保证并发编程的可见性和有序性。 想要理解volatile为什么能确保可见性,就先理解Java中的内存模型是什么样的。 看完下面...
  • 多线程 高并发

    2013-06-27 08:29:41
    在开发过程中自己编写的多线程并发程序组件源代码共享给大家,里面有测试的例子,提供给大家学习,希望大家多提宝贵意见~
  • 该资源是10多线程高并发项目源码,项目功能齐全,代码书写规范详细,值得大家下载下来学习借鉴保存都可以,非常的有价值
  • 实现多线程,都不部分的人,都只知道两种,继承Thread类和实现runnable接口。对Callable接口实现多线程很陌生,我们看通过源码学习,了解callable是怎么一回事。
  • 现在面试动不动多线程高并发,请问各位大神,你们项目中的多线程高并发是如何用的,哪些场景下用的?我项目中主要是在做消息推送的时候用到过,消息存到kafka队列里面,然后创建一个线程池创建线程去处理每个消息。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 16,956
精华内容 6,782
关键字:

多线程高并发