精华内容
下载资源
问答
  • 高并发编程

    2020-10-29 10:24:57
    高并发编程 并发:多个线程操作相同的资源,保证线程安全,合理使用资源。 高并发:服务能同时处理很多请求,提高程序性能。 Java内存模型(Java Memory Model,JMM) 堆:可以动态的生成内存大小,垃圾收集器会自动...

    高并发编程

    并发:多个线程操作相同的资源,保证线程安全,合理使用资源。

    高并发:服务能同时处理很多请求,提高程序性能。

    Java内存模型(Java Memory Model,JMM)

    堆:可以动态的生成内存大小,垃圾收集器会自动回收,存储速度慢。

    栈:存储块,仅次于计数器,存在的大小必须固定。

    Java内存模型同步八种操作

    锁定(Lock)

    作为与主内存的变量,把一个变量标识为一条线程独占状态。

    解锁(unlock)

    作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量菜可以被其他线程锁定。

    read(读取)

    作用于主内存的变量,把一个变量值从主内存传输到线程的工作内存中,以便以后的load动作使用。

    load(载入)

    作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。

    use(使用)

    作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎。

    assign(赋值)

    作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量。

    store(存储)

    作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的wirte的操作。

    write(写入)

    作用于主内存的变量,它把store操作从工作内存中的一个变量的值传送到主内存的变量中。

    并发的优势与风险

    优势:速度,设计,资源利用。

    风险:安全性,活跃性,性能。

    总结

    CPU多级缓存:缓存一致性,乱序执行优化

    java内存模型:JMM规定,抽象结构,同步八种操作及规则。

    Java并发的优势与风险

    展开全文
  • │ 高并发编程第一阶段01讲、课程大纲及主要内容介绍.wmv │ 高并发编程第一阶段02讲、简单介绍什么是线程.wmv │ 高并发编程第一阶段03讲、创建并启动线程.mp4 │ 高并发编程第一阶段04讲、线程生命周期以及...
  • │ 高并发编程第一阶段01讲、课程大纲及主要内容介绍.wmv │ 高并发编程第一阶段02讲、简单介绍什么是线程.wmv │ 高并发编程第一阶段03讲、创建并启动线程.mp4 │ 高并发编程第一阶段04讲、线程生命周期以及...
  • 我是全网最硬核的高并发编程作者,CSDN最值得关注博主,全网最硬核的基于可视化的多数据源数据异构中间件作者,也许现在就是,也是不久的将来就是,大家同意吗?

    原创不易,小伙伴们给个一键三连呀~~

    大家好,我是冰河~~

    正如文章标题所言,我是全网最硬核的高并发编程作者,CSDN最值得关注博主,全网最硬核的基于可视化的多数据源数据异构中间件作者,也许现在就是,也是不久的将来就是。

    为啥这样说?因为我写了一本全网首部完全免费并且开源的,全方位涵盖源码分析、基础案例、实战案例、面试和系统架构的《深入理解高并发编程》电子书,发行四个月全网累计下载17万+。 而且,我在CSDN上写了近1700篇超硬核原创技术文。我开源的基于可视化的多数据源数据异构中间件mykit-data超30家公司采用,承担着其核心业务数据的全量、增量(定时、实时)同步,经受住了生产环境中大数据量场景下的全量、增量(定时、实时)同步的考验。 其他的,且听我慢慢道来。

    有很多小伙伴是最近才关注我的,对我的了解不多,那我就先厚着脸皮来个自我介绍吧。

    自我介绍

    我是冰河,《海量数据处理与大数据技术实战》,《MySQL技术大全:开发、优化与运维实战》畅销书作者,CSDN博客专家,mykit系列开源框架作者,基于最终消息可靠性的开源分布式事务框架mykit-transaction-message作者,基于可视化多数据源数据异构中间件mykit-data作者。多年来致力于分布式系统架构、微服务、分布式数据库、分布式事务与大数据技术的研究。在高并发、高可用、高可扩展性、高可维护性和大数据等领域拥有丰富的架构经验。对Hadoop,Storm,Spark,Flink等大数据框架源码进行过深度分析,并具有丰富的实战经验,目前在研究云原生领域。

    我的CSDN唯一博客主页:https://binghe.blog.csdn.net/ ,我在CSDN上更新了将近1700篇硬核原创技术文。

    在这里插入图片描述
    我在CSDN发表的博文内容涵盖架构、研发、后端、前端、测试、运维、渗透、大数据、云计算、云原生等多个领域。

    在这里插入图片描述

    我也是《海量数据处理与大数据技术实战》,《MySQL技术大全:开发、优化与运维实战》两本畅销书的作者。而且写这两本畅销书我只用了5个月时间,一本500多页,一本700多页,从基础、开发、优化、运维到架构,全程实战干货。我个人觉得我可能是业界写书最快的作者(应该就是最快的吧,你们说呢?)。

    在这里插入图片描述

    同时,业余时间我也维护着一些个人的开源项目,每个开源项目都是我个人在业余时间持续更新和维护,比较知名的有:

    GitHub:
    文章收录:
    https://github.com/sunshinelyz/technology-binghe
    可视化多数据源异构中间件:
    https://github.com/sunshinelyz/mykit-data
    分布式序列号(ID)生成器:
    https://github.com/sunshinelyz/mykit-serial
    基于最终消息可靠性分布式事务框架:
    https://github.com/sunshinelyz/mykit-transaction-message
    精准延迟任务与消息队列框架:
    https://github.com/sunshinelyz/mykit-delay

    Gitee:
    文章收录:
    https://gitee.com/binghe001/technology-binghe
    可视化多数据源异构中间件:
    https://gitee.com/binghe001/mykit-data
    分布式序列号(ID)生成器:
    https://gitee.com/binghe001/mykit-serial
    基于最终消息可靠性分布式事务框架:
    https://gitee.com/binghe001/mykit-transaction-message
    精准延迟任务与消息队列框架:
    https://gitee.com/binghe001/mykit-delay

    其中,mykit-data可视化多数据源异构中间件已经在超过30家公司的生产环境使用,经受住了生产环境高并发,大流量下的数据全量、增量(定时、实时)同步的考验。

    好了,自我介绍完毕,小伙伴们如果对这些开源项目感兴趣,自己可以下载源码研究下,遇到不懂的问题可以提Issues,也可以在CSDN上私信我,我看到后都会回复大家。

    接下来,我们就聊聊为何我是全网最硬核的高并发编程作者。

    写作背景

    2020年疫情期间,大部分企业都实行居家办公的策略,而我,也在家里办公。这就省去了每天坐公交上下班的时间,无形当中,节省了很多时间,也就意味着有了更多的时间来自由支配。那多出来的时间干啥?想来想去,还是写一些关于高并发编程的技术文章吧,因为在网上公开的关于高并发编程的知识要么很零散,要么就是收费的,根本无法满足我对高并发编程的需求。 怎么办?既然网上没有,那我就自己写吧。

    艰难的写作过程

    整个写作过程其实还是挺艰难的,需要 梳理写作思路,画脑图,看源码,画流程图,写作,排版 等一系列的流程。一篇文章从构思到写作完成真的要付出很多心血。

    开始最难的还是确定【精通高并发系列】专栏的范围,到底要写哪些内容,内容到底要写到何种深度,想来想去,我画了一个简单的脑图,先把要写的并发基础知识写出来。

    开始,我就先写并发编程的三大核心问题:协作、互斥和分工。按照脑图深入并发编程的类库、框架原理和源码。尽管很难,那段时间还是坚持着日更的节奏。每天几乎都是 5点半起床写文,8点吃早饭,9点按时在家上班打卡,下午6点在家下班打卡,吃晚饭。然后晚上7点钟又开始写文,日复一日的坚持。

    写到后面,内容就不止这些了。加入了 源码分析、基础案例、实战案例、面试、系统架构 等关于高并发的硬核知识,真正的实现从原理、源码到实战、面试与系统架构的全方位高并发技术。

    给大家看下就【精通高并发系列】一个专题我写的文章底稿。

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    几乎涉及的所有图片都是我自己手动画出来的。

    在这里插入图片描述

    整理电子书

    写了这么多,后来有小伙伴给我提议,让我整理成电子书,方便他们查看。我觉得这个提议好,于是我又花了将近一周的时间把写的高并发相关的知识整理成了电子书。整理完电子书,又开始纠结了,这本书叫啥名字呢?最终取了一个读者给的名字《深入理解高并发编程(第1版)》。为啥是第1版?没错,就是第1版,因为后面我还要不断的迭代更新它,后续还会推出第2版、第3版、第4版等等。努力把它打造成为业界最厉害的免费开源的高并发编程电子书。

    整部书的结构不变,还是分为: 源码分析、基础案例、实战案例、面试、系统架构 五大部分。

    这部电子书一经问世,没想到短短的四个月时间,累计下载量就已突破17万+。目前下载量仍在持续上升。这部电子书为什么会这么火呢?它到底涵盖哪些内容呢?接下来,我们一探究竟。

    关于电子书

    《深入理解高并发编程(第1版)》这部PDF大部分内容来自冰河的CSDN博客【精通高并发系列】专栏,整体大约36W字,优化排版后共计315页。

    在这里插入图片描述

    涵盖:源码分析篇、基础案例篇、实战案例篇、面试篇和系统架构篇。 这应该是全网最牛的免费开源的高并发编程电子书了吧! 全书结构如下所示。

    全书内容概览

    全书涵盖的内容如下所示。

    在这里插入图片描述

    书籍部分内容如下:

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    整部电子书从源码解析到系统架构一气呵成,系统架构篇则作为整部PDF的最后一个部分,在系统架构篇中,我在CSDN博客中挑选了两篇最具代表性的文章《高并发秒杀系统架构解密,不是所有的秒杀都是秒杀 !》和《高并发分布式锁架构解密,不是所有的锁都是分布式
    锁! !
    》。通过对秒杀系统架构和分布式锁架构的深入剖析,使得小伙伴们在更高的思维层次来深入理解高并发编程,并做到在实际项目中灵活运用。

    后续规划

    目前,我也在规划继续更新【精通高并发系列】文章,规划推出《深入理解高并发编程》第2版,第3版等等。我也重新细致的画了高并发编程的脑图。

    在这里插入图片描述

    注意:脑图建议放大后查看。

    后面要做的,就是持续更新文章啦。

    说了这么多,该如何下载这部四个月全网下载量突破17万+的电子书呢?别着急,冰河为你整理好,上传到CSDN啦。

    https://download.csdn.net/download/l1028386804/18209878

    也上传了一份到我的百度网盘。

    链接:https://pan.baidu.com/s/1f81RPMNOyIv3mgc-mTCBQg
    提取码:xhbq

    最后,还是标题那个问题,我是全网最硬核的并发编程作者,是最值得大家关注的CSDN博主,也是全网最硬核的基于可视化的多数据源数据异构中间件作者,大家同意吗?欢迎大家在文末留言交流~~

    如果文章对你有点帮助,小伙伴们给个一键三连~~

    展开全文
  • 分享课程——Java高并发编程,构建并发编程知识体系,提升面试成功率;带你构建完整的并发与高并发知识体系,一旦形成完整的知识体系,无论是跳槽面试还是开发,你都将是最快脱颖而出的那一个 本课程将结合大量图示...
  • 在实际开发过程中往往会出现许多高并发场场景,秒杀,强红包,抢优惠卷等; 其中: 秒杀场景的特点就是单位时间涌入用户量极大,商品数少,且要保证不可超量销售; 秒杀产品的本质就是减库存; 秒杀场景常用的...

    环境:

    jdk1.8;spring boot2.0.2;Maven3.3

    摘要说明:

    在实际开发过程中往往会出现许多高并发场场景,秒杀,强红包,抢优惠卷等;

    其中:

    秒杀场景的特点就是单位时间涌入用户量极大,商品数少,且要保证不可超量销售;

    秒杀产品的本质就是减库存;

    秒杀场景常用的解决方案有限流、削峰、拓展等

    本篇以秒杀场景为依据来主要从代码开发的角度阐述从无锁——》排他锁——》共享锁——》缓存中间件的一步步升级来不断完善及优化;同时也针对整体架构提出一些优化方案;

    步骤:

    1.准备高并发测试工具类

    引入高并发编程的工具类:java.util.concurrent.CountDownLatch(发令枪)来进行模拟大批量用户高并发测试;

    java.util.concurrent.CountDownLatch(发令枪):一个同步辅助类,控制一组线程的启动,当一组线程未完全准备好之前控制准备好一个或多个线程一直等待。犹如倒计时计数器,调用CountDownLatch对象的countDown方法就将计数器减1,当计数到达0时,则意味着这组线程完全准备好。此时通知所有等待者即整组线程同时开始执行。

    package com.example.demo_20180925;
    
    import java.util.Map;
    import java.util.concurrent.CountDownLatch;
    
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
    import org.springframework.test.context.junit4.SpringRunner;
    
    import com.example.demo_20180925.pojo.ProductInfo;
    import com.example.demo_20180925.service.ProductInfoService;
    
    @RunWith(SpringRunner.class)
    // 引入SpringBootTest并生成随机接口
    @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
    public class Demo20180925ApplicationTests {
    
    	// 商品代码
    	private static final String CODE = "IPONE XR";
    	// 商品总数
    	private static final Long PRODUCTCOUNT = (long) 1000;
    	// 并发人数
    	private static final int USER_NUM = 1000;
    	// 发令枪;用于模拟高并发
    	private static CountDownLatch countDownLatch = new CountDownLatch(USER_NUM);
    	// 计数器,用于记录成功购买客户人数
    	private static int successPerson = 0;
    	// 计数器,用于记录卖出去对的商品个数
    	private static int saleOutNum = 0;
    	// 计数器,用于记录处理总时间
    	private static long doTime = 0;
    	// 计数器,用于记录处理最长时间
    	private static long maxTime = 0;
    	@Autowired
    	ProductInfoService productInfoService;
    
    	@Before
    	public void init() {
    		// 初始化库存
    		ProductInfo productInfo = new ProductInfo();
    		productInfo.setProductCode(CODE);
    		productInfo.setProductCount(PRODUCTCOUNT);
    		this.productInfoService.updateFirst(productInfo);
    	}
    
    	@Test
    	public void testSeckill() throws InterruptedException {
    		// 循环初始换USER_NUM个请求实例
    		for (int i = 0; i < USER_NUM; i++) {
    			new Thread(new BuyProduct(CODE, (long) 3)).start();
    			if (i == USER_NUM) {
    				Thread.currentThread().sleep(2000);// 最后一个子线程时休眠两秒等待所有子线程全部准备好
    			}
    			countDownLatch.countDown();// 发令枪减1,到0时启动发令枪
    		}
    		Thread.currentThread().sleep(30 * 1000);// 主线程休眠10秒等待结果
    		// Thread.currentThread().join();
    		System.out.println("购买成功人数:" + successPerson);
    		System.out.println("销售成功个数:" + saleOutNum);
    		System.out.println("剩余个数:"
    				+ productInfoService.selectByCode(CODE).getProductCount());
    		System.out.println("处理时间:" + doTime);
    	}
    
    	public class BuyProduct implements Runnable {
    		private String code;
    		private Long buys;
    
    		public BuyProduct(String code, Long buys) {
    			this.code = code;
    			this.buys = buys;
    		}
    
    		public void run() {
    			try {
    				countDownLatch.await();// 所有子线程运行到这里休眠,等待发令枪指令
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    			// 直接减库存
    			// Map<String, Object> map = productInfoService.update(code, buys);
    
    			// 加排他锁(悲观锁)后减库存
    			Map<String, Object> map = productInfoService.selectForUpdate(code,
    					buys);
    
    			// 根据版本号加乐观锁减库存
    			// Map<String, Object> map =
    			// productInfoService.updateByVersion(code,
    			// buys);
    
    			// 根据库存加乐观锁减库存
    			// Map<String, Object> map = productInfoService.updateByBuys(code,
    			// buys);
    
    			// 根据缓存减库存
    			// Map<String, Object> map = productInfoService.updateByRedis(code,
    			// buys);
    			if ((boolean) map.get("result")) {
    				synchronized (countDownLatch) {
    					// 更新库存成功,修改购买成功人数及销售产品数量
    					successPerson++;
    					// 记录总购买成功人数
    					saleOutNum = (int) (saleOutNum + buys);
    					// 记录总消费时间
    					doTime = doTime + (long) map.get("time");
    					// 记录最大时间
    					if (maxTime < (long) map.get("time")) {
    						maxTime = (long) map.get("time");
    					}
    				}
    			}
    		}
    	}
    }
    

    2.无锁开发

    许多开发对高并发编程及数据库锁的机制不是很理解,开发时就很简单的查询库存然后更新库存即select——》update;此时在高并发的环境下测试就会出现产品过多销售的情况;

    @Service
    public class ProductInfoServiceImpl implements ProductInfoService {
    	@Autowired
    	ProductInfoMapper productInfoMapper;
    	@Autowired
    	RedisTemplate<String, Object> redisTemplate;
    	private static long threadCount = 0;
    
    	/**
    	 * 根据产品代码查询产品
    	 */
    	@Override
    	public ProductInfo selectByCode(String code) {
    		return productInfoMapper.findByCode(code);
    	}
    
    	/**
    	 * 初始化库存
    	 */
    	@Override
    	public boolean updateFirst(ProductInfo productInfo) {
    		redisTemplate.opsForHash().put("productCount",
    				productInfo.getProductCode(), productInfo.getProductCount());
    		return productInfoMapper.updateForFirst(productInfo.getProductCode(),
    				productInfo.getProductCount()) > 0 ? true : false;
    	}
    
    	/**
    	 * 直接减库存
    	 */
    	@Override
    	public Map<String, Object> update(String code, Long buys) {
    		threadCount++;
    		System.out.println("开启线程:" + threadCount);
    		Date date = new Date();
    		Map<String, Object> map = new HashMap<String, Object>();
    		ProductInfo productInfo = productInfoMapper.findByCode(code);
    		if (productInfo.getProductCount() < buys) {
    			map.put("result", false);
    			Date date1 = new Date();
    			map.put("time", date1.getTime() - date.getTime());
    			return map;
    		}
    		map.put("result", productInfoMapper.update(code, buys) > 0 ? true
    				: false);
    		Date date1 = new Date();
    		map.put("time", date1.getTime() - date.getTime());
    		return map;
    	}
    }

    我们执行下可以看到结果:

    1000个人,每个人购买3个商品,商品总数1000个,理论上购买成功人数应该为333,商品销售成功个数应该为999;

    但实际购买成功人数560,销售商品个数为1680;远远的过度销售;

    开启线程:993
    开启线程:991
    开启线程:996
    开启线程:995
    开启线程:997
    开启线程:998
    开启线程:999
    开启线程:1000
    购买成功人数:560
    销售成功个数:1680
    剩余个数:-680
    处理时间:2424240
    最大处理时间:3220

    2.锁的分类

    数据库锁的机制从不同角度出发可进行不同的分类:

    颗粒度上可划分(基于mysql):

    • 表级锁:表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持;特点:开销小,加锁快;不会出现死锁;锁定粒度大,发出锁冲突的概率最高,并发度最低。
    • 行级锁:行级锁是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。特点:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
    • 页级锁:页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。BDB支持页级锁;特点:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般

    级别上进行划分:

    • 共享锁:又称读锁,是读取操作创建的锁。其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。
    • 排他锁:又称写锁,如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。

    使用方式上进行划分:

    • 乐观锁:是一种并发控制的方法。它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚
    • 悲观锁:是一种并发控制的方法。它可以阻止一个事务以影响其他用户的方式来修改数据。如果一个事务执行的操作都某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作。悲观并发控制主要用于数据争用激烈的环境,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。

    操作上进行划分:

    • DML锁:用于保护数据的完整性,其中包括行级锁(Row Locks (TX锁))、表级锁(table lock(TM锁))。
    • DDL锁:用于保护数据库对象的结构,如表、索引等的结构定义。其中包排他DDL锁(Exclusive DDL lock)、共享DDL锁(Share DDL lock)、可中断解析锁(Breakable parse locks)

    3.排他锁(悲观锁)开发

    通过上面对锁的机制介绍之后我们可以看到,排他锁可以很好的解决我们上面商品多销售的问题;排他锁的本质即排队执行;

    mysql的排他锁的用法为:SELECT ... FOR UPDATE;

    在查询语句后面增加FOR UPDATE,Mysql会对查询结果中的每行都加排他锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请排他锁,否则会被阻塞。所以它本质上也是一个行级锁;

    	/**
    	 * 根据产品代码查询产品信息;排他锁
    	 * 
    	 * @param code
    	 *            产品代码
    	 * @return
    	 */
    	@Select("SELECT id,version,product_code as productCode,product_name as productName, product_count AS productCount FROM product_info WHERE product_code = #{code} for update")
    	ProductInfo selectForUpdate(@Param("code") String code);

    业务层:

    	/**
    	 * 加排他锁减库存
    	 */
    	@Transactional
    	@Override
    	public Map<String, Object> selectForUpdate(String code, Long buys) {
    		threadCount++;
    		System.out.println("开启线程:" + threadCount);
    		Date date = new Date();
    		Map<String, Object> map = new HashMap<String, Object>();
    		ProductInfo productInfo = productInfoMapper.selectForUpdate(code);
    		if (productInfo.getProductCount() < buys) {
    			map.put("result", false);
    			Date date1 = new Date();
    			map.put("time", date1.getTime() - date.getTime());
    			return map;
    		}
    		map.put("result", productInfoMapper.update(code, buys) > 0 ? true
    				: false);
    		Date date1 = new Date();
    		map.put("time", date1.getTime() - date.getTime());
    		return map;
    	}

    执行结果:

    开启线程:979
    开启线程:980
    开启线程:981
    开启线程:982
    开启线程:983
    开启线程:984
    购买成功人数:333
    销售成功个数:999
    剩余个数:1
    处理时间:4101
    最大处理时间:160

    结果可以看到排他锁可以很好的控制住商品数量的销售;但排他锁的本质是排队,如果业务复杂或者并发人数过多的情况下会产生超时现象;

    4.乐观锁开发

    乐观锁的本质上并没有使用数据库本身的锁机制;只是在提交的那一刻通过sql查询条件来约束更新;

    常规的是乐观锁一般有两种:

    一种是破坏表的业务接口添加版本号(version)或者时间戳(timestamp );

    一种是使用业务本身做约束;

    版本号形式共享锁:

    /**
    	 * 根据购买数量及版本号减少库存
    	 * 
    	 * @param code
    	 *            产品代码
    	 * @param buys
    	 *            购买数量
    	 * @param version
    	 *            版本信息
    	 * @return
    	 */
    	@Update("update product_info SET product_count=product_count - #{buys},version=version+1 WHERE product_code = #{code} and version = #{version}")
    	int updateByVersion(@Param("code") String code, @Param("buys") Long buys,
    			@Param("version") Long version);

    业务层:共享锁当约束条件不满足之后,需要在嵌套调用,直到满足条件或商品销售成功才停止;

    	/**
    	 * 根据版本号加乐观锁减库存
    	 */
    	@Override
    	public Map<String, Object> updateByVersion(String code, Long buys) {
    		Date date = new Date();
    		Map<String, Object> map = new HashMap<String, Object>();
    		try {
    			threadCount++;
    			System.out.println("开启线程:" + threadCount);
    			ProductInfo productInfo = productInfoMapper.findByCode(code);
    			if (productInfo.getProductCount() < buys) {
    				map.put("result", false);
    				Date date1 = new Date();
    				map.put("time", date1.getTime() - date.getTime());
    				return map;
    			}
    			if (productInfoMapper.updateByVersion(code, buys,
    					productInfo.getVersion()) > 0 ? true : false) {
    				map.put("result", true);
    				Date date1 = new Date();
    				map.put("time", date1.getTime() - date.getTime());
    				return map;
    			}
    			waitForLock();
    			return updateByVersion(code, buys);
    		} catch (Exception e) {
    			System.err.println(e);
    			map.put("result", false);
    			Date date1 = new Date();
    			map.put("time", date1.getTime() - date.getTime());
    			return map;
    		}
    
    	}
        // 错峰执行
    	private void waitForLock() {
    		try {
    			Thread.sleep(new Random().nextInt(10) + 1);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    	}

    执行结果:

    开启线程:1612
    开启线程:1613
    开启线程:1614
    开启线程:1615
    开启线程:1616
    购买成功人数:333
    销售成功个数:999
    剩余个数:1
    处理时间:428636
    最大处理时间:3215

    结果可以看到乐观锁锁可以很好的控制住商品数量的销售;但我们也可以看到乐观锁会导致线程的循环执行,若业务要求先到先得的话一定程度上是不满足的;

    通过业务销量来实现共享锁:

    	/**
    	 * 根据购买数量及剩余库存减少库存
    	 * 
    	 * @param code
    	 *            产品代码
    	 * @param buys
    	 *            购买数量
    	 * @return
    	 */
    	@Update("update product_info SET product_count=product_count - #{buys} WHERE product_code = #{code} and (product_count - #{buys})>0")
    	int updateByBuys(@Param("code") String code, @Param("buys") Long buys);

    业务层:由于我们使用商品数量本身作为约束;故不需要做嵌套调用

    	/**
    	 * 根据库存加乐观锁减库存
    	 */
    	@Override
    	public Map<String, Object> updateByBuys(String code, Long buys) {
    		threadCount++;
    		System.out.println("开启线程:" + threadCount);
    		Date date = new Date();
    		Map<String, Object> map = new HashMap<String, Object>();
    		try {
    			ProductInfo productInfo = productInfoMapper.findByCode(code);
    			if (productInfo.getProductCount() < buys) {
    				map.put("result", false);
    				Date date1 = new Date();
    				map.put("time", date1.getTime() - date.getTime());
    				return map;
    			}
    			if (productInfoMapper.updateByBuys(code, buys) > 0 ? true : false) {
    				map.put("result", true);
    			}else{
    				map.put("result", false);
    			}
    			Date date1 = new Date();
    			map.put("time", date1.getTime() - date.getTime());
    			return map;
    //			waitForLock();
    //			return updateByBuys(code, buys);
    		} catch (Exception e) {
    			System.err.println(e);
    			map.put("result", false);
    			Date date1 = new Date();
    			map.put("time", date1.getTime() - date.getTime());
    			return map;
    
    		}
    	}

    执行结果:

    开启线程:456
    购买成功人数:333
    销售成功个数:999
    剩余个数:1
    处理时间:487387
    最大处理时间:2759

    5.缓存中间件

    上述的使用数据库的两种锁机制是可以很好的解决问题;但若是我们不断增加并发数,就可以看到对数据库会造成很大的压力;实际生产环境,数据库本身的资源压力就很大;在这种关键入口处最好引入缓存数据库来过滤请求限流减少数据库压力;

    本篇使用redis缓存,其中redis缓存和spring boot的集合这里就不赘述,请自行查看源码;

    这里使用的是redis数据库的Hash类型中的Redis Hincrby 命令:用于为哈希表中的字段值加上指定增量值,增量值可为负。

    HINCRBY KEY_NAME FIELD_NAME INCR_BY_NUMBER 
    	@Autowired
    	RedisTemplate<String, Object> redisTemplate;    
    
    	@Override
    	public Map<String, Object> updateByRedis(String code, Long buys) {
    		threadCount++;
    		System.out.println("开启线程:" + threadCount);
    		Date date = new Date();
    		Map<String, Object> map = new HashMap<String, Object>();
    		long count = Long.valueOf(redisTemplate.opsForHash().get(
    				"productCount", code)
    				+ "");
    		if (count > 0) {
    			count = Long.valueOf(redisTemplate.opsForHash().increment(
    					"productCount", code, -buys));
    			if (count >= 0) {
    				map.put("result", true);
    				Date date1 = new Date();
    				map.put("time", date1.getTime() - date.getTime());
    				return map;
    			} else {
    				map.put("result", false);
    				Date date1 = new Date();
    				map.put("time", date1.getTime() - date.getTime());
    				return map;
    			}
    		} else {
    			map.put("result", false);
    			Date date1 = new Date();
    			map.put("time", date1.getTime() - date.getTime());
    			return map;
    		}
    	}

    执行结果:

    开启线程:926
    开启线程:924
    开启线程:923
    购买成功人数:333
    销售成功个数:999
    剩余个数:1000
    处理时间:69702
    最大处理时间:462

    6.拓展

    上述代码开发的演变不可能就此解决高并发带给体统的压力;这里就阐述下从整体架构上去提高服务的并发能力:

    展现层

    禁止重复提交:用户提交之后按钮置灰,禁止重复提交 
    用户限流:在某一时间段内只允许用户提交一次请求

    代理层

    动静分离:将所有静态资源放在apache httpd或者nginx服务下;减轻后端服务压力;本身处理静态资源能里也大于tomcat;

    页面压缩、缓存:针对静态页面设置压缩机制及缓存机制,减少流量峰值;

    服务层

    服务拆分及部署:系统拆分多个服务减少耦合度,同时达到热点隔离及进程隔离的效果;

    集群部署:服务拆分后可根据每个服务的并发量进行横向拓展;同时也达到集群隔离的效果;

    代码开发:乐观锁+缓存

    中间件层

    缓存中间件:通常读多写少的场景及集中写入的都可以使用缓存中间件减少数据库压力

    消息中间件:通过消息中间件可以将并发任务插入队列分批执行

    数据层

    数据库集群:通过集群来提高数据库并发能力

    读写分离:通过读写分离来分担数据库压力

    7.源码

    上述代码源码地址:https://github.com/cc6688211/demo_20180925.git

     

    展开全文
  • 多线程与高并发编程之基础知识(上)

    万次阅读 多人点赞 2018-10-01 21:48:25
    但是,想要使用好多线程这把利刃,还需要掌握好多线程编程的基础知识,从而做到得心应手地使用多线程进行性能程序的开发! 多线程的应用场景 程序中出现需要等待的操作,比如网络操作、文件IO等,可以利用...

    前言

    几乎所有的程序员都知道,现代操作系统进行资源分配的最小单元是进程,而操作系统进行运算调度的最小单元是线程,其实,在Linux中线程也可以看作是一种轻量级的进程,那么线程是包含于进程之中的,是进程中实际的运作单位;同一进程中的多个线程共用同一块内存空间,而不同的线程又拥有独立的栈内存用以存放线程本地数据;

    大家都知道,现在的计算机动辄就是多处理器核心的,而每一个线程同一时间只能运行在一个处理器上,那么如果程序采用单线程进行开发,那么就不能充分利用多核处理器带来的优势;所以为了充分利用多核处理器的资源来提高程序的执行性能,多线程编程变得越来越重要,比如对于计算密集型任务,使用一个线程可能需要100秒,但是,如果使用十个线程共同完成,那么需要的时间可能只有10秒左右;如果你是使用Java开发程序的,那么你很幸运,因为Java是内置多线程编程模型的;但是,想要使用好多线程这把利刃,还需要掌握好多线程编程的基础知识,从而做到得心应手地使用多线程进行高性能程序的开发!


    多线程的应用场景

    • 程序中出现需要等待的操作,比如网络操作、文件IO等,可以利用多线程充分使用处理器资源,而不会阻塞程序中其他任务的执行
    • 程序中出现可分解的大任务,比如耗时较长的计算任务,可以利用多线程来共同完成任务,缩短运算时间
    • 程序中出现需要后台运行的任务,比如一些监测任务、定时任务,可以利用多线程来完成

    自定义线程的实现

    处于实用的角度出发,想要使用多线程,那么第一步就是需要知道如何实现自定义线程,因为实际开发中,需要线程完成的任务是不同的,所以我们需要根据线程任务来自定义线程,JDK为我们的开发人员提供了三种自定义线程的方式,供实际开发中使用,来开发出符合需求的多线程程序!

    以下是线程的三种实现方式,以及对每种实现的优缺点进行分析,最后是对这三种实现方式进行总结;

    方式一:继承Thread类

    package com.thread;
    //通过继承Thread类实现自定义线程类
    public class MyThread extends Thread {
    	//线程体
        @Override
        public void run() {
            System.out.println("Hello, I am the defined thread created by extends Thread");
        }
        public static void main(String[] args){
            //实例化自定义线程类实例
            Thread thread = new MyThread();
            //调用start()实例方法启动线程
            thread.start();
        }
    }
    

    优点:实现简单,只需实例化继承类的实例,即可使用线程
    缺点:扩展性不足,Java是单继承的语言,如果一个类已经继承了其他类,就无法通过这种方式实现自定义线程

    方式二:实现Runnable接口

    package com.thread;
    public class MyRunnable implements Runnable {
        //线程体
        @Override
        public void run() {
            System.out.println("Hello, I am the defined thread created by implements Runnable");
        }
        public static void main(String[] args){
        	//线程的执行目标对象
            MyRunnable myRunnable = new MyRunnable();
            //实际的线程对象
            Thread thread = new Thread(myRunnable);
            //启动线程
            thread.start();
        }
    }
    

    优点:

    • 扩展性好,可以在此基础上继承其他类,实现其他必需的功能
    • 对于多线程共享资源的场景,具有天然的支持,适用于多线程处理一份资源的场景

    缺点:构造线程实例的过程相对繁琐一点

    方式三:实现Callable接口

    package com.thread;
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    public class MyCallable implements Callable<String> {
        @Override
        public String call() throws Exception {
            return "Hello, I am the defined thread created by implements Callable";
        }
        public static void main(String[] args){
            //线程执行目标
            MyCallable myCallable = new MyCallable();
            //包装线程执行目标,因为Thread的构造函数只能接受Runnable接口的实现类,而FutureTask类实现了Runnable接口
            FutureTask<String> futureTask = new FutureTask<>(myCallable);
            //传入线程执行目标,实例化线程对象
            Thread thread = new Thread(futureTask);
            //启动线程
            thread.start();
            String result = null;
            try {
                //获取线程执行结果
                result = futureTask.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            System.out.println(result);
        }
    }
    

    优点:

    • 扩展性好
    • 支持多线程处理同一份资源
    • 具备返回值以及可以抛出受检查异常

    缺点:

    • 相较于实现Runnable接口的方式,较为繁琐

    小结

    我们对这三种方式进行分析,可以发现:方式一和方式二本质上都是通过实现Runnable接口并重写run()方法,将接口实现类的实例传递给Thread线程类来执行线程体(run()方法中的实现),这里将Runnable接口实现类的实例作为线程执行目标,供线程Thread实例执行;对于方式三,其实也是这样的,由于Thread类只能执行Runnable接口实现类的执行目标,所以需要对Callable接口的实现类进行包装,包装成Runnable接口的实现类(通过实现了Runnable接口的FutureTask类进行包装),从而使得Thread类能够接收Callable接口实现类的实例,可见这里使用了适配器模式!

    综上所述,三种实现方式都存在着一个使用范式,即首先实现线程执行目标对象(包含线程所要执行的任务),然后将目标对象作为构造参数以实例化Thread实例,来获得线程!本质上都是实现一个线程体,由Thread来执行线程体,达到开启线程执行任务的效果!但是,三种实现方式各有优缺点,使用时,应该结合具体需求来选用合适的实现方式进行开发!


    线程的生命周期

    经过上面的代码演示,我们知道了线程如何实现,但是如果我们想要更好地使用线程,还需要对程序运行中线程的状态以及状态之间的转换(即线程的生命周期)有所了解,这样才能在多线程程序运行出现问题时,分析问题产生的原因,从而快速准确地定位并解决问题!

    首先,看一下Thread类中给出的关于线程状态的说明:

    	/**
         * 线程生命周期中的的六种状态
         * NEW:还没有调用start()的线程实例所处的状态
         * RUNNABLE:正在虚拟机中执行的线程所处的状态
         * BLOCKED:等待在监视器锁上的线程所处的状态
         * WAITING:等待其它线程执行特定操作的线程所处的状态
         * TIMED_WAITING:等待其它线程执行超时操作的线程所处的状态
         * TERMINATED:退出的线程所处的状态
         * 给定时间点,一个线程只会处于以下状态中的一个,这些状态仅仅是虚拟机层面的线程状态,并不能反映任何操作系统中线程的状态
         */
        public enum State {
            //还没有调用start()开启的线程实例所处的状态
            NEW, 
            //正在虚拟机中执行或者等待被执行的线程所处的状态,但是这种状态也包含线程正在等待处理器资源这种情况
            RUNNABLE,
            // 等待在监视器锁上的线程所处的状态,比如进入synchronized同步代码块或同步方法失败
            BLOCKED,
            // 等待其它线程执行特定操作的线程所处的状态;比如线程执行了以下方法: Object.wait with no timeout、Thread.join with no timeout、 LockSupport.park
            WAITING,
           // 等待其它线程执行超时操作的线程所处的状态;比如线程执行了以下方法: Thread.sleep、Object.wait with timeout
           //Thread.join with timeout、LockSupport.parkNanos、LockSupport.parkUntil
            TIMED_WAITING,
            //退出的线程所处的状态
            TERMINATED;
        }
    
    • 新建(New):当线程实例被new出来之后,调用start()方法之前,线程实例处于新建状态
    • 可运行(Runnable):当线程实例调用start()方法之后,线程调度器分配处理器资源之前,线程实例处于可运行状态或者线程调度器分配处理器资源给线程之后,线程实例处于运行中状态,这两种情况都属于可运行状态
    • 等待(Waitting):当线程处于运行状态时,线程执行了obj.wait()或Thread.join()方法、Thread.join、LockSupport.park以及Thread.sleep()时,线程处于等待状态
    • 超时等待(Timed Waitting):当线程处于运行状态时,线程执行了obj.wait(long)、Thread.join(long)、LockSupport.parkNanos、LockSupport.parkUntil以及Thread.sleep(long)方法时,线程处于超时等待状态
    • 阻塞(Blocked):当线程处于运行状态时,获取锁失败,线程实例进入等待队列,同时状态变为阻塞
    • 终止(Terminated):当线程执行完毕或出现异常提前结束时,线程进入终止状态

    线程的状态转换

    上面也提到了,某一时间点线程的状态只能是上述6个状态中的其中一个;但是,线程在程序运行过程中的状态是会发生变化的,由一个状态转变为另一个状态,那么下面给出线程状态转换图帮助我们清晰地理解线程的状态转变过程:
    在这里插入图片描述


    上面我们已经对线程的实现以及线程的状态有了较为清晰的认识,那么通过上述内容,我们也可以发现其实有很多方法,我们并没有详细地介绍,比如start()、yield()、wait()、notify()、notifyAll()、sleep()、join()等等,这些方法大多来源于JDK中Thread类这一关键的线程类中,下面结合Thread类的源码看一下,多线程编程中经常遇到的方法有哪些,以及这些方法的用途;

    线程类Thread源码

    实例同步方法:join()

        /**
         * 等待调用此方法的线程执行结束
         * @throws  InterruptedException 如果任何线程中断了当前线程,将会抛出此异常,同时将中断标志位清除
         */
        public final void join() throws InterruptedException {
            join(0);
        }
        /**
         * 最多等待millis毫秒,时间一到无论是否执行完毕,都会返回
         * 如果millis为0,那么意味着一直等到线程执行完毕才会返回
         * 此方法的实现是基于循环检测当前线程是否存活来判断是否调用当前实例的wait方法来实现的
         * @param  millis 等待时间
         * @throws  IllegalArgumentException 非法参数异常
         * @throws  InterruptedException 如果任何线程中断了当前线程,将会抛出此异常,同时将中断标志位清除
         */
        public final synchronized void join(long millis) throws InterruptedException {
            long base = System.currentTimeMillis();
            long now = 0;
            if (millis < 0) {
                throw new IllegalArgumentException("timeout value is negative");
            }
            if (millis == 0) {
                while (isAlive()) {
                    wait(0);
                }
            } else {
                while (isAlive()) {
                    long delay = millis - now;
                    if (delay <= 0) {
                        break;
                    }
                    wait(delay);
                    now = System.currentTimeMillis() - base;
                }
            }
        }
        /**
         * 线程执行结束之前最多等待millis毫秒nanos纳秒
         * 此方法基于循环判断isAlive返回值来决定是否调用wait方法来实现
         * 随着一个线程终止,将会调用notifyAll方法 
         * 所以建议不要在当前实例上调用 wait、 notify、 notifyAll
         */
        public final synchronized void join(long millis, int nanos)
                throws InterruptedException {
            if (millis < 0) {
                throw new IllegalArgumentException("timeout value is negative");
            }
            if (nanos < 0 || nanos > 999999) {
                throw new IllegalArgumentException(
                        "nanosecond timeout value out of range");
            }
            if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
                millis++;
            }
            join(millis);
        }
    

    中断方法以及检测中断方法和判活方法:

        /**
         * 中断当前线程
         * 如果当前线程阻塞在Object的wait()、wait(long)、wait(long, int),或者
         * join()、join(long)、join(long, int)以及sleep(long)、sleep(long, int)等方法
         * 那么将会清除中断标志位并受到一个中断异常
         * 非静态方法
         */
        public void interrupt() {
            if (this != Thread.currentThread())
                checkAccess();
            synchronized (blockerLock) {
                Interruptible b = blocker;
                if (b != null) {
                    interrupt0();           // Just to set the interrupt flag
                    b.interrupt(this);
                    return;
                }
            }
            interrupt0();
        }
        /**
         * 检测当前线程是否已经被中断,此方法会清除当前线程的中断标志
         * 也就是说,如果这个方法被连续调用两次,并且第一次调用之前,线程被中断过,那么第一次调用返回true,第二次返回false
         * @return  <code>true</code> 如果当前线程已经被中断,返回true,否则返回false     
         * 静态方法
         */
        public static boolean interrupted() {
            return currentThread().isInterrupted(true);
        }
        /**
         * 检测当前线程是否已经被中断,此方法不会清除当前线程的中断标志
         * 非静态方法
         */
        public boolean isInterrupted() {
            return isInterrupted(false);
        }
        /**
         * 根据参数值决定是否在判断中断标志位之后清除标志位
         * 实例方法
         */
        private native boolean isInterrupted(boolean ClearInterrupted);
        /**
         * 检测一个线程是否还存活,存活指的是已经启动但还没有终止
         * 实例方法
         */
        public final native boolean isAlive();
    

    结尾

    到此为止,本文已经对线程的使用场景、实现方式以及生命周期、状态转换过程以及线程类常用的方法进行了介绍,但是上面只是从概念上对线程的相关知识进行叙述,但是实际开发中,我们使用多线程是为了解决实际问题,比如如何实现多个线程共同完成一个耗时长的任务或者如何实现多个线程交互完成一个大型任务,在这些实际应用线程的过程中,会遇到问题,下面将在多线程与高并发编程基础知识(下)一文中给出多线程开发中,面临的问题以及对这些问题进行分析,并介绍常用的解决方案;最后,希望读者在阅读之后,如果发现文中出现不正确的地方还请指出,大家一同探讨和学习,共同成长^^!

    展开全文
  • 高并发编程之AtomicReference讲解

    万次阅读 多人点赞 2019-02-20 21:02:35
    一、AtomicReference介绍 ①.AtomicReference和AtomicInteger非常类似,不同之处就在于AtomicInteger是对整数的封装,而AtomicReference则对应普通的对象引用。也就是它可以保证你在修改对象引用时的线程安全性。...
  • JAVA高并发编程

    万次阅读 多人点赞 2018-05-07 09:30:26
    同步代码块的同步粒度更加细致,是商业开发中推荐的编程方式。可以定位到具体的同步位置,而不是简单的将方法整体实现同步逻辑。在效率上,相对更。 锁定临界对象 同步代码块在执行时,是锁定 object 对象。...
  • 高并发编程学习与实战,系统的学习并发编程的知识。 目录结构 atomic:原子类 container:同步容器与并发容器 不可变: 发布:对象的发布 sync:线程同步 threadLocal:螺纹封闭 unsafe-class:常用的线程不安全类
  • Python3高并发编程

    2021-06-11 09:09:21
    多进程multiprocessing模块提供了本地和远程的并发性,有效地通过全局解释锁(Global Interceptor Lock,GIL)来使用进程,可以充分利用硬件的多处理器来进行工作。还等什么,赶快让你的程序高效的跑起来吧。
  • 【多线程高并发编程】二 实现多线程的几种方式

    万次阅读 多人点赞 2020-02-17 23:32:54
    是指通过软件优化和硬件(CPU)的方式,同时并发运行多个线程(任务)。更好的运行系统的资源。 例如 ,社长,很久以前,接到boss的提的一个业务,需要开发一个充电桩管理物联网管理平台,实现通过网站,查看各个充电...
  • Java高并发编程,构建并发编程知识体系,提升面试成功率-附件资源
  • 高并发编程之CopyOnWriteArrayList介绍

    万次阅读 多人点赞 2019-02-28 10:28:59
    ①、CopyOnWriteArrayList,写数组的拷贝,支持效率并发且是线程安全的,读操作无锁的ArrayList。所有可变操作都是通过对底层数组进行一次新的复制来实现。 ②、CopyOnWriteArrayList适合使用在读操作远远大于写...
  • 高并发编程基础

    千次阅读 2020-03-21 09:39:02
    ##高并发编程基础练习############################################### 练习1: 自定义容器,提供新增元素(add)和获取元素数量(size)方法。启动两个线程。 线程1向容器中新增10个数据。线程2监听容器元素数量,当...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 15,024
精华内容 6,009
关键字:

高并发编程