《java并发编程的艺术》读书笔记二
此文仅作为读书笔记
volatile的应用
volatile的定义与实现原理
volatile的定义
java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排它锁单独获得这个变量。如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。
在了解volatile的实现原理之前,最好先熟悉与其实现原理相关的CPU术语与说明
术语 | 英文单词 | 术语描述 |
---|---|---|
内存屏障 | memory barriers | 是一组处理器指令,用于实现对内存操作的顺序限制 |
缓冲行 | cache line | CPU高速缓存中可以分配的最小存储单位。处理器填写缓存行时会加载整个缓存行,现代CPU一秒钟需要执行几百次CPU指令 |
原子操作 | atomic operations | 不可中断的一个或一系列操作 |
缓存行填充 | cache line fill | 当处理器识别到从内存中读取操作数是可缓存的,处理器读取整个高速缓存行到适当的缓存(L1,L2,L3的或所有) |
缓存命中 | cache hit | 如果进行高速缓存行填充操作的内存位置仍然是下次处理器访问的地址时,处理器从缓存中读取操作数,而不是从内存中读取 |
写命中 | write hit | 当处理器将操作数写回到一个内存缓存的区域时,它首先会检查这个缓存的内存地址是否在缓存行中,如果存在一个有效的缓存行,则处理器将这个操作数写回到缓存,而不是写回到内存,这个操作被称为写命中 |
写缺失 | write misses the cache | 一个有效的缓存行被写入到不存在的内存区域 |
volatile是如何保证可见性的呢?通过工具获取JIT编译器生成的汇编指令来查看对volatile进行写操作时,发现指令前会加上lock前缀。lock前缀的指令在多喝处理器下会引发一下两件事:
- 将当前处理器缓存行的数据写回到系统内存
- 这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效
为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存后再进行操作,但操作完不知道何时会写到内存。如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是,就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题。所以,在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。
volatile的两条实现原则:
- lock前缀指令会引起处理器缓存回写到内存
- 一个处理器的缓存回写到内存会导致其他处理器的缓存无效
《java并发编程的艺术》读书笔记一
并发编程的挑战
并发编程的目的是为了让程序运行得更快,但是,并不是启动更多的线程就一定能让程序运行得更快,在进行并发编程时,会面临非常多的挑战,常见的问题如下:
- 上下文切换,影响执行效率
- 死锁,导致业务无法正常进行
- 硬件软件资源的限制
上下文切换
单核处理器也支持多线程执行代码,CPU是通过给每个线程分配CPU时间片来实现这个机制。时间片一般是几十毫秒(ms),非常短暂,所以我们无法感知,感觉多个线程就是同时执行的。CPU通过时间片分配算法来循环执行任务,某个任务执行一个时间片后会切换到下一个任务,在切换前会保存上一个任务的执行状态,在下次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换。
HashMap源码学习
网上关于HashMap的博客实在是太多了,我再系统地来讲HashMap肯定没有现有的博客讲的好,所以我决定换一个角度,就从我们日常使用的地方开始,完整的走一遍HashMap的源码,应该会有不一样的收获。
先看下面一段代码
1 | Map map = new HashMap(); |
平时开发中很常见的用法,需要注意的是,由于使用了阿里巴巴规范插件,new HashMap的地方是有如下提示的
Filter servlet interceptor 的执行顺序问题
背景
最近在做一个移动端h5项目的后端应用,接口联调时前端反应说登录有问题,先简单介绍一下我们的权限实现,jwt加spring security,使用redis做的session中心存放token。
1.前端请求登录接口,核对成功返回用户信息和token,同时将此信息保存于redis中
2.访问后端接口需要将token保存于header中,请求先经过安全Filter,会从token中解析出subject,然后去redis中查询看是否存在对应token
3.token相同则继续往下执行,token不同则说明登录已过期,返回401
具体情况是登录成功进入数据页面之后就会请求初始数据,但是请求初始数据时就会返回401,提示未登录然后跳转回登录页面,出现问题第一时间肯定是想办法甩锅,于是马上用postman进行接口自测,果然,postman测接口一切正常。怀疑是不是前端vue的状态管理有问题导致传的token不对,和redis中查到的token不同导致的,然而经过核对,传的的确是登录接口返回的token。锅又回到了我这边。
springBoot接口中直接返回图片数据
起因
最近在做涉及到分享推广的业务,需要由业务员分享二维码进入推广页面,由于是新项目,前期预算和用量都有限,没有搭建对象存储服务,所以决定使用后台服务动态生成二维码图片直接图片数据并返回。
首先是二维码的生成,决定使用google的zxing,毕竟google的东西还是不错的,maven添加依赖如下:1
2
3
4
5
6
7
8
9
10
11
12<!-- https://mvnrepository.com/artifact/com.google.zxing/core -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.3.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.zxing/javase -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.3.3</version>
</dependency>
继续查zxing的使用方法,发现大多数都是生成二维码然后写成图片文件的,不太适合我现在的情况。
类似这种