JUC之一——基础概念


多线程无论是服务端还是客户端,都是核心模块。只知道线程池的用法和几个关键参数的含义,too young to simple,系统学习JUC的内容,用你我能懂的话理解JUC。

1.JUC包

在 Java 5.0 提供了 java.util.concurrent(简称JUC)包,在此包中增加了在并发编程中很常用的工具类。新增主要包括三部分:
Atomic包、lock包、synchronized。后面这三部分会完全延展开来研究!
在这里插入图片描述

2.并发不同端侧重点

1.Android端

文件下载、网络接口请求、图片下载、本地数据读取、复杂业务逻辑等等,简而概之,就是一切耗时操作,而并发掌握的核心目的是为了规避并发带来的卡顿问题。

2.服务端

(1)庞大外部用户请求访问,所形成的高QPS(Queries Per Second)
台服务器每秒能够相应的查询次数,是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准, 即每秒的响应请求数,也即是最大吞吐能力。
(2)应用服务所需要维持的三高服务体系:高并发、高可用、高性能
(3)耗时业务操作

3.那些混淆名词

有图有真相,随便网上某东某猫网站打开电脑配置图,这些指标你了解吗?
在这里插入图片描述
图1.芯片图
在这里插入图片描述
图2电脑性能参数图
大家专注的look一下红箭头的标注

1.多核心

问个问题:4核心是是4个芯片吗?不用回答我,自己在心里问问自己!
多核心:指单芯片多处理器。由美国斯坦福大学提出来的。将大规模并发处理器中的SMP(多处理器)集成到到同一个芯片内,各个处理器并行执行不同的进程。依靠多个CPU(处理器)同时并行地运行程序是实现超高速计算的一个重要方向,称为并行处理

2.芯片、CPU、半导体

芯片是“集成电路”的俗称。集成电路有模拟集成电路和数字集成电路,如果一片集成电路(芯片)中既有模拟电路又有数字电路,则称其为数模混合集成电路。

CPU是中央处理器:包含运算器和控制器,是数字电路。如果将运算器和控制器集成在一片集成电路上,就称之为微处理器。目前人们将中央处理器与微处理器已经混为一谈了。CPU是一种数字芯片,只是众多芯片中的一类。

半导体是芯片的载体:生产 CPU 等芯片的材料是半导体,现阶段主要的材料是硅 Si ,这是一种非金属元素,从化学的角度来看,由于它处于元素周期表中金属元素区与非金属元素区的交界处,所以具有半导体的性质,适合于制造各种微小的晶体管,是目前最适宜于制造现代大规模集成电路的材料之一

实际上图1的芯片(物理上)是有4个核心(CPU),这个图片可能存在问题,因为CPU和总线的比例应该是1:2,为啥1:2就要涉及解析来要讲的超线程技术。

3.多线程

Simultaneous Multithreading.剪成SMT.让同一个处理器上的多个线程同步执行并共享处理器的执行资源。
操作系统是通过线程来执行任务的,一般情况下CPU与线程1:1,但Intel引入超线程技术后,使核心数与线程数形成1:2关系。
进程是最小的资源管理单位、线程是最小的任务执行单位。

4.超线程

在上个概念中,已经提到intel的超线程技术,这个技术是什么呢?
MultiThreading多线程就是在一个单个的处理核心内同时运行多个工作线程的技术。现在intel是支持到单核双线程,正如图2性能参数图一样,四核八线程就是应用了多线程技术。

5.CPU时间片轮转机制

根据先进先出原则,排成队列(就绪队列),调度时,将CPU分配给队首进程,让其执行一个时间段(称为:时间片),时间片通常为10-100ms数量级,当执行的时间片用完时,会由计时器发出时钟中断请求,调度程序便据此来停止该进程的执行,并将它排到队列末尾,然后再把CPU重新分配给当前队列的队首进程,同理如此往复。图片看的更清晰,上图:
在这里插入图片描述
1.时间片大小
时间片设得太短会导致过多的进程切换,降低了CPU效率,舍得为太长又可能引起对短的交互请求的响应变差。将时间片设为100ms通常比较折中,不过,也根据具体情况,通常10ms-100ms之间。

6.并发与并行

并行举个例子有条高速公路有并排8个车道,那么最大的并行车辆就是8。CPU也是这个原理,一个CPU相当于一个车道。
并发呢?则要加个单位时间,如果没有单位时间就没有意义。一个CPU,理论上只能运行一个进程,由于运行速度笔记比较快,我们就让一心多用。利用时间片轮转进程调度算法实现并发,运行多个进程。
在这里插入图片描述

7、CUP生产厂商

英特尔/Intel -i系列
AMD-r系列
高通-ARM架构

4.模型

1.CPU物理内核架构

在这里插入图片描述

CPU包括运算器、控制器、寄存器。主内存因为跟不上CPU的算算速度,所以,引入了高速缓存区。

2.内存模型&内存区域/内存结构

在这里插入图片描述
内存模型,在每个线程执行时,因为CPU执行速度快,主内存跟不上,所以,每个线程执行时都有高速缓存区。整体的内存模型如上图。
因为存在内存模型和内存结构的概念模糊不清,所以,这里也把虚拟机的内存结构找了出来。
在这里插入图片描述

只是为了做一下内存结构和内存模型的区别,如果大家对内存结构感兴趣,可以看下JVM一篇就够了

3.验证内存模型

  new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("loop start");
                while (!isFinish){
					//此处如果Thread.sleep(1000),则不会出现并发问题,因为sleep时,释放工作内存内容,缓存数据失效,重新加载
                }
                System.out.println("loop end");
            }
        }).start();
		//保证切换到多线程,实现高并发,为什么呢?
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("change isFinish start");
                isFinish = true;
                System.out.println("change isFinish end ");

            }
        }).start();

结果:
在这里插入图片描述
正如所看到,会一直无法获取到isFinish的值。
为什么一定要加个线程sleep呢?为了实现多个线程并发处理。正如所看到,并发机制会带来三大问题

5.高并发三大问题

1.可见性

多个线程间的数据通信及数据同步问题!

2.有序性

指令重排可能会带来的线程间的处理问题

3.原子性

多线程间执行结果不一致问题

大家看到内存模型,数据的流向是怎么样的呢?它的操作最小单位又是什么样的呢?为什么要讲原子性?
在这里插入图片描述

1. 八大原子操作:

(1)read(读取):从主内存中读取数据
(2)load(载入):从主内存读取到的数据写入工作内存
(3)use(使用):从工作内存读取数据来计算
(4)assign(赋值):将计算好的值重新赋值到工作内存当中
(5)stroe(存储):将工作内存数据写入主内存
(6)write(写入)将存入的数据变量值复制给主内存中的共享变量
(7)lock(锁定):将主内存变量加锁
(8)unlock(解锁):将主内存变量解锁
对于八大原子操作,大家会有困惑的应该是read和load的过度,实际上,在工作内存和主内存之间是有个控制总线的,控制总线是用于连接工作内存和主内存的,物理上来讲,就是排线,可以理解为一个消息队列但是,它是隔离两个内存的,read和write都是由它来实现。

2.内存模型中总线协议

1.缓存一致协议(MESI)

多个CPU从主内存读取同一个数据到各自的高速缓存,当其中某个CPU修改了缓存的数据,该数据马上同步给主内存。其他的CPU通过总线唤醒机制可以感知到数据的变化从而将自己缓存的数据失效。缓存一致协议,主要是Intel和AMD采用方式

2.缓存加锁

缓存锁的核心机制是遵循与缓存一致性协议,当一个处理器的缓存回写到内存会导致其他处理的缓存失效。ARM架构下使用的就是它。

6.Volatile可见性底层原理分析

7.指令重排序

1.指令重排优化

在计算机执行指令的顺序在经过程序 编译器编译之后形成的指令序列。
一般情况下,CPU和编译器为了提升程序执行的效率,会按照一定规则允许进行指令优化,在某些情况下,优化会带来一些执行的逻辑问题。
在并发执行情况下,会发生二义性,按照不同的执行逻辑,会得到不同的结果信息。

2.指令重排规则

对于指令先后顺序会导致最终结果不一致,则不会发生指令重排,反之,如果不会发生结果不一致则会发生重排。

3.Happens-Before

7.高并发下双重检测锁DCL指令引发的问题

8.内存屏障

本质是:CPU在指令优化是给与一个标记位置,碰到此位置不进行优化。

9.巨人肩膀

1.10分钟看懂CPU构造原理

2.[QPS&TPS]

### 如何快速掌握 Java JUC 并发包 Java 的 `java.util.concurrent` (简称 JUC) 是处理并发编程的核心工具集。以下是关于其使用方法及常见问题解决方案的详细介绍。 #### 1. 基础概念理解 JUC 提供了许多高级工具来简化并发程序的设计和实现。核心类库包括锁 (`Lock`)、信号量 (`Semaphore`)、计数器 (`CountDownLatch`, `CyclicBarrier`) 和线程池管理等[^1]。 开发者应熟悉以下基本概念: - **synchronized 关键字**: 它通过内置锁机制保护临界区代码,防止多个线程同时访问共享资源[^3]。 - **volatile 关键字**: 确保变量可见性和禁止指令重排序[^2]。 - **原子操作**: 利用 CAS(Compare And Swap)技术提供无锁算法支持,例如 `AtomicInteger` 或 `AtomicReference`[^4]。 #### 2. 实践中的常用组件详解 ##### (1)显式锁 - ReentrantLock 相比隐式的 synchronized, 显式锁提供了更灵活的功能,比如尝试加锁、定时等待以及公平策略设置等功能。 ```java import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Counter { private int count = 0; private final Lock lock = new ReentrantLock(); public void increment() { lock.lock(); // 加锁 try { count++; } finally { lock.unlock(); // 解锁 } } public int getCount() { return count; } } ``` ##### (2)条件队列 - Condition Condition 对象允许一个线程在某个特定条件下被挂起,并由另一个线程唤醒它。 ```java class BoundedBuffer { final Lock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count; public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal(); } finally { lock.unlock(); } } public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; notFull.signal(); return x; } finally { lock.unlock(); } } } ``` ##### (3)线程间协作 - CountDownLatch & CyclicBarrier 这些工具用于协调一组线程的行为,在某些场景下非常有用。 - **CountDownLatch**: 让主线程阻塞直到其他辅助线程完成工作后再继续运行。 - **CyclicBarrier**: 可重复使用的屏障,适用于多轮迭代计算的情况。 #### 3. ABA 问题及其解决方案 ABA 问题是由于内存地址复用引起的错误状态转换现象。可以通过引入版本号或者时间戳的方式加以规避;具体到 JDK 中,则推荐使用带有标记位的对象封装形式——即 `AtomicStampedReference` 来替代简单的 `AtomicInteger`/`AtomicLong` 类型数据结构。 #### 4. 性能调优技巧 对于高负载环境下的应用来说,减少不必要的上下文切换尤为重要。合理配置线程池大小、选用适合的任务调度方式都是提升效率的有效手段之一。另外还需注意避免过度依赖于悲观锁控制逻辑而造成性能瓶颈的发生。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值