并发编程-01并发初窥

2021-08-17 15:50:15 浏览数 (1)

文章目录

  • 引言
  • 思维导图
  • 基础知识构建
  • 涉及的知识点一览
  • 高并发处理思路与手段一览
  • 并发初窥
    • 概念
    • 并发问题模拟
  • 代码

引言

说来惭愧,一直没有系统的梳理过并发编程的知识,这次借着学习_Jimin_老师的《Java并发编程与高并发解决方案》课程的机会,结合以往工作中的使用,好好的梳理下并发编程与高并发的知识,形成一个较为完善的并发编程知识体系。


思维导图


基础知识构建


涉及的知识点一览


高并发处理思路与手段一览


并发初窥

概念

并发: 同时拥有两个或者多个线程,如果程序在单核处理器上运行多个线程将交替地换入或者换出内存,这些线程是 同时“存在”的,每个线程都处于执行过程中的某个状态,如果运行在多核处理器上,此时,程序中的每个线程都 将分配到一个处理器核上,因此可以同时运行。

高并发( High Concurrency): 是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指通过设计保证系统能够"同时并行处理"很多请求

  • 并发:多个线程操作相同的资源,保证线程安全,合理使用资源
  • 高并发:服务能同时处理很多请求,提高程序性能

并发问题模拟

首先说明如下代码是存在并发问题的,这里是为了抛出问题,后续给出解决办法.

其中用到的ExecutorService、Semaphore和CountDownLatch等先有个印象,后续深入探讨,更多说明见注释

Spring Boot搭建的工程,详见github ConcurrencyMaster

代码语言:javascript复制
package com.artisan;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

import lombok.extern.slf4j.Slf4j;

/**
 * 首先说明如下代码是存在并发问题的,这里是为了抛出问题,后续给出解决办法
 * 
 * 要求: 10000个请求,同一时间允许500个请求同时执行 即每次允许500个请求同时执行
 * 
 * 实现:使用线程池和信号量和countDownLatch模拟客户端总共发出了1万个请求,每次允许500个请求同时执行,观察一共执行了个多少个请求
 * 
 * @author yangshangwei
 *
 */
@Slf4j
public class CountCaculatorDemo {

	// 请求总数
	private static int clientTotal = 10000;
	// 同一时间执行的请求数
	private static int threadTotal = 1;
	
	// 计数标识
	private static int count = 0;

	public static void main(String[] args) throws InterruptedException {
		// 创建一个可缓存线程池,如果线程池长度超过实际需要,可灵活回收空闲线程,若无可回收的线程,则新建线程。
		ExecutorService executorService = Executors.newCachedThreadPool();

		// 信号量 同一时间允许threadTotal个请求同时执行 即初始化threadTotal个信号量
		final Semaphore semaphore = new Semaphore(threadTotal);
		
		// CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行
		// 定义clientTotal个线程需要执行完,主线程才能继续执行
		final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
		// 创建clientTotal个线程,模拟clientTotal个请求
		for (int i = 0; i < clientTotal; i  ) {
			// 执行一个线程 ,count加1
			executorService.execute(() -> {
				try {
					// 获取许可,获取许可后执行add方法, 在获得许可之前,一直将线程阻塞
					semaphore.acquire();
					add();
					// 释放许可,并将其返回给信号量
					semaphore.release();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				// 【在循环内】每当一个线程完成了自己的任务后,计数器的值就会减1,这里指的是初始化CountDownLatch给定的clientTotal减一
				countDownLatch.countDown();
			});
			
		}
		
		// 主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。
		// 这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。
		// 当全部线程都调用了countDown方法,count的值等于0,然后主线程就能通过await()方法,恢复执行自己的任务。
		countDownLatch.await();
		
		// 关闭线程池
		executorService.shutdown();
		// 打印总数
		log.info("执行总数:{}", count);
	}
	
	private static void add() {
		count  ;
	}

}

执行多次,可以发现绝大部分情况下结果都不为1万,当然了也有可能为1万。

如果将threadTotal 改为1 ,即同一时刻只允许一个线程执行,再次运行,无论如何运行多少次,结果都是10000。

说明多线程确实影响到了执行结果,后面继续将结合Java内存模型分析多线程是如何影响执行结果的。


代码

https://github.com/yangshangwei/ConcurrencyMaster

0 人点赞