下边都是我之前整理的md,往博客上存一下而已
Java基础
谈谈对volatile字段的理解。
volatile是JVM虚拟机提供的一个轻量级的同步机制
1、保证可见性
2、不保证原子性
3、禁止指令重排序=有序性
volatile的应用
单例模式 -> 一共有六种(见代码!SingletonDemo.JMM将内存划分为两部分,一部分是线程共享区域(Java堆、方法区)一部分是线程非共享区域(Java栈、程序计数器、本地方法栈),)
懒汉
饿汉
JMM(Java内存模型---Java memory model)
是一种抽象的模型并不真实存在,是一种规范,定义了各个变量的访问形式
JMM对于同步的规定:
1、线程加锁前必须读取主内存中变量的最新值
2、线程解锁前必须将变量值更新到主内存中
3、加锁解锁是同一把锁
JMM三大特性:
1、可见性
什么是主内存?
主内存就是线程共享区域 主内存对应了硬件的内存条。也就是4G8G的内存条
JMM将内存划分为两部分,一部分是线程共享区域(Java堆、方法区)一部分是线程非共享区域(Java栈、程序计数器、本地方法栈),
每个线程执行时JVM都会为其分配一个空间作为这个线程私有的工作内存,也就是Java栈。而某个线程如果想修改共享区域内存储的一个
变量时,必须先将变量拷贝到自己的私有内存中才可以修改,修改完了私有内存的变量值最终把结果更新到主内存中,也就是线程共享区域。
线程不能在主内存也就是线程共享区域修改变量的值,线程之间进行通讯传值,必须通过主内存来实现。这其实也就是一种线程间传递消息
的一种方式。
电脑各个部件读取速度
硬盘<内存(Redis)<CPU(但是cpu只管计算不管存储)
在CPU跟Redis之间还有一个缓存(cache)。CPU计算完成之后放入缓存,然后从缓存放入内存
为什么volatile可以保证可见性?
因为volatile修饰的变量只能是变量名与值都放在线程共享区域中的,线程修改主内存的变量都需要先将主内存的变量拷贝到自己的私有工作内存中,修改完了再写到主内存。
volatile变量使得线程在读取变量时,读取到的变量永远是最新的。
volatile会加一个内存屏障,强制刷出CPU内的各种数据,这样就保证了其他线程在读取变量时是最新的版本。
2、原子性
不可分割、完整性、某个线程正在做某个业务的时候,中间不可以被抢占加塞或者被分割,需要整体完整,要么同时成功要么同时失败。
保证数据的完整一致性,多线程操作某个数据的时候数据并不会出现那种比如写覆盖的情况
原子性解决方法?见代码!
1、sychronized
2、JUC包下的Atmoic类!->CAS->zixunsuo、unSafe类
3、有序性
指令重排序
你写的代码的执行顺序并不一定跟实际上代码的执行顺序相等,特别是在多线程情况下。
指令重排序的时候要考虑代码的数据依赖性,比如你还没有定义int x;你就直接执行x++;这显然是没有考虑代码的依赖性。
并不是每一次都会发生指令重排序。
1、单线程环境下不用关心指令重排序
2、多线程环境下因为线程交替执行,由于编译器优化重排的存在。两个线程中使用的变量能否保持一致性是无法确定的结果无法预测。
3、依赖性,比如你还没有定义int x;你就直接执行x++;这显然是没有考虑代码的依赖性。
volatile通过插入一个内存屏障来禁止对内存屏障前后的命令进行指令重排序
CAS
是什么?
比较并交换CompareAndSwap,原子操作类的compareAndSet方法底层就是用了CAS,如果想修改某个原子类对象A的值,(对象A的值在底层是用参数ValueOffSet来存储的),compareAndSet方法的参数是except值与update值,传入之后调用unsafe的CAS方法,将except值与内存中的值相互比较,如果A与except值相等的话就修改A为update值并且返回Ture,否则就不修改返回false
见代码!
UnSafe类
提供了硬件级别的原子性操作,Java不能直接访问底层操作系统,UnSafe类是Java访问底层操作系统的入口
悲观锁与乐观锁
从思想上来看Sychronized是属于悲观锁的,而CAS是属于乐观锁。
乐观锁适用于冲突少的时候,读多写少的情况
悲观锁适用于冲突多的时候,读少写多的情况
乐观锁与悲观锁是并发条件下的并发控制的两种策略。
悲观锁:每次去拿数据的时候都会认为有人修改了要拿的数据,所以使用悲观锁严防死守。这样别人在拿这个数据的时候就会block直到它拿到锁。(行锁、表锁、读锁、写锁)
一锁锁全表。悲观锁可以用拍他锁跟共享锁组合实现。悲观锁可以通过拍他锁与共享锁配合使用来实现。
乐观锁:每次去拿数据的时候都会认为数据是最新的没有被修改过,所以不会上锁,但是会在更新的时候上锁给要更新的树句,判断一下其他人有没有更新数据。(CAS)
两种实现方式,一种不加版本号会导致ABA问题,一种加版本号解决了ABA问题
共享锁与拍他锁
共享锁:SELECT * FROM TABLE WHERE ID = 1 FOR UPDATE
排他锁:SELECT * FROM TABLE WHERE ID = 1 LOCK IN SHARE MODE
共享锁又称为读锁,即为多个事务对于同一数据可以共享一把锁。都能访问到数据但是只能读不能改。
拍他锁又称为写锁,即为一个事务对于同一数据上了拍他锁之后就不能再给他上任何其他的锁,
update、delete、insert都是给涉及到的数据加了拍他锁。select语句默认不会加锁,但是可以通过select ...for update加上拍他锁,select ... lock in share mode加上共享锁,所以在一个事务 给某些涉及到的数据加上拍他锁之后就不能对数据进行update、delete、insert了,也不能使用加锁的select语句查询数据。但是可以select...from...因为select普通模式下不会有锁
加了共享锁的数据就不能在其他事务上加其他的锁,比如拍他锁啥的。
表级锁与行级锁
1. 只有通过索引条件检索数据时,InnoDB才会使用行级锁,否则会使用表级锁。
2. 即使是访问不同行的记录,如果使用的是相同的索引键,会发生锁冲突。
3.如果数据表建有多个索引时,可以通过不同的索引锁定不同的行。
不同的数据库引擎采用的不同粒度的锁,MySQL采用的InnoDB存储引擎可以支持表级锁与行级锁,不支持页锁
表行页是按照粒度来划分的。
行级锁包括共享锁与排他锁
表级锁:开销小、加锁快、不死锁、冲突高、并发低、粒度大、适用于并发不高查询为主少量更新的小型web系统
行级锁:开销大、加锁慢、会死锁、冲突低、并发高、粒度小、适用于高并发对事务完整性要求较高的系统
死锁:
死锁的条件:
互斥:某资源一次只能分配给一个资源来使用
占有且等待:某进程占有某资源并且等待其他资源
不抢占:某进程在释放某资源的锁之前,其他的进程不能强行剥夺此资源的锁
循环等待:所有的进程循环等待资源的释放
如何避免死锁:
预防:确保系统永远不会进入死锁状态。破坏占有且等待、不抢占、循环等待的三个条件
避免:使用某资源前进行判断是否会出现死锁。只允许不会产生死锁的进程申请资源。银行家算法
检测与解除:
MySQL事务:
事务的属性
原子性:事务是最小的执行单位,要么全都执行完毕要么全部不执行,不会出现执行一半的情况
一致性:事务开始到结束的期间,数据库的数据都保持一致
隔离性:保证事务在不受外部并发条件影响的情况下独立执行
持久性:事务完成后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。
事务的常见问题:
更新丢失:多个人同时往github提交对某个文件修改的代码,会出现冲突。多个事务基于最初选定的值进行操作会发生更新覆盖的问题
脏读:事务A读取了事务B正在修改但是尚未提交的数据,事务B回滚导致事务A读取到的数据一致性出现问题
不可重复读:事务A读取最初的数据后又读取了事务B提交修改的数据或者删除的数据,导致违背了事务的隔离性
幻读:事务A第一次读取到最初的数据后又读组了事务B新增的数据,违背了事务的隔离性
事务的隔离级别
+------------------------------+---------------------+--------------+--------------+--------------+
| 隔离级别 | 读数据一致性 | 脏读 | 不可重复 读 | 幻读 |
+------------------------------+---------------------+--------------+--------------+--------------+
| 未提交读(Read uncommitted) | 最低级别 | 是 | 是 | 是 |
+------------------------------+---------------------+--------------+--------------+--------------+
| 已提交读(Read committed) | 语句级 | 否 | 是 | 是 |
+------------------------------+---------------------+--------------+--------------+--------------+
| 可重复读(Repeatable read) | 事务级 | 否 | 否 | 是 |
+------------------------------+---------------------+--------------+--------------+--------------+
| 可序列化(Serializable) | 最高级别,事务级 | 否 | 否 | 否 |
+------------------------------+---------------------+--------------+--------------+--------------+
什么是ABA问题?
x y
线程1 except A,update B
线程2 except A, update B
线程3 except B, update A
线程执行顺序为 1x、2x、1y、3x、3y、2y结果为A—>B->A->B
去银行取钱,某个动作点击了两次,比如余额为 100 取钱 50 动作点击了两次,分别为线程1,2,此时某人汇钱给此账户 50 元为线程3按照上边的顺序执行后结果为 50元,显然是错误的,应该是100元
加个版本号可以解决问题。