JAVA锁
在Java中的锁主要是用于保障线程在多并发的情况下数据的一致性。就是实现并发的原子性。
在多线程编程中为了保证数据的一致性,我们通常需要在使用对象或者调用方法之前加锁,这时如果有其他线程也需要使用该对象或者调用该方法,则首先要获得锁,如果某个线程发现锁正在被其他线程使用,就会进入阻塞队列等待锁的释放,直到其他线程执行完成并释放锁,该线程才有机会再次获取锁并执行操作。这样做可以保障了在同一时刻只有一个线程持有该对象的锁并修改该对象,从而保障数据的安全性。
线程要不要锁住同步资源
悲观锁:锁住
悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。
传统的关系型数据库里边就用到了很多这种锁机制, 比如行锁,表锁等,读锁, 写锁等,都是在做操作之前先上锁
适用于“写入”操作比较频繁的场景
- 操作的类型
- 读锁
读锁【共享锁】:针对同一份数据,多个操作可以同时进行而不会相互影响
对MyISAM表的读操作(加读锁),不会阻塞其他进程对同一表的读请求,但会阻塞对同一表的写请求。只有当读锁释放后,才会执行其他进程的写操作。 - 写锁
写锁【排他锁】:当前写操作没有完成前,他会阻断其他写锁和读锁.
对MyISAM表的写操作(加写锁),会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其他进程的读写操作。
数据库加写锁的时候可能会添加表锁,行锁和间隙锁
- 操作的粒度
- 表锁
表锁会锁定整张表,如果当前有用户正在执行写操作并且获取了写锁,这可能导致整张表被锁定,阻塞其他用户的读写操作。如果用户执行的是读操作,则会获取读锁,此时其他用户的并发读操作将被接受,写操作会被阻塞。
- 偏向MyISAM存储引擎,开销小,加锁快;无死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低
- 行锁
行锁的粒度是在每一条行数据,这意味行锁可以尽可能的支持并发处理,相应的行锁开销也会比较大。并且,在InnoDB中的行锁是针对索引加的锁,不是针对记录加的锁,并且该索引不能失效,否则行锁将会自动升级为表锁。
- 偏向InnoDB存储引擎,开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高
- 间隙锁
定义:当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。
间隙锁(Gap Lock)是Innodb在提交下为了解决幻读问题时引入的锁机制。在可重复读隔离级别下,数据库锁是通过行锁和间隙锁(开区间)共同组成来实现的。(-∞,5)5(5,10)10(10,15)15(15,20)20(20,25)25(25,+supernum] (其中supernum是数据库维护的最大的值。) - 临键锁
间隙锁和行锁结合一起就是临键锁。左开右闭。(-∞,5](5,10](10,15](15,20](20,25](25,+supernum]
乐观锁:不锁住
顾名思义,就是很乐观, 每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。
适用于“读取”操作比较频繁的场景
- 实现方式
- 使用版本标识来确定读到的数据与提交时的数据是否一致。提交后修改版本标识,不一致时可以采取丢弃和再次尝试的策略
- CAS机制
CAS ,当多个线程尝试使用CAS 同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败, 失败的线程并不会被挂起,而是被告知这次竞争中失败, 并可以再次尝试。CAS 操作中包含三个操作数—— 需要读写的内存位置( V)、进行比较的预期原值( A)和拟写入的新值(B)。如果内存位置V 的值与预期原值A 相匹配,那么处理器会自动将该位置值更新为新值B。否则处理器不做任何操作。
一个线程中的多个流程能不能获取同一把锁
可重入锁:能
可重入性:就是一个线程不用释放,可以重复的获取一个锁n次,只是在释放的时候,也需要相应的释放n次。
(例:A线程在某上下文中或得了某锁,当A线程想要在次获取该锁时,不会因为锁已经被自己占用,而需要先等到锁的释放),假设A线程即获得了锁,又在等待锁的释放,就会造成死锁。
- ReentrantLock
上几次锁,就需要手动释放几次 - synchronized
无需释放锁,synchronized会自动释放锁
非可重入锁:不能
多个线程竞争锁时需不需要排队
ReentrantLock的公平/非公平策略
公平锁:排队
ReentrantLock的公平策略:FairSync【公平策略:hasQueuedPredecessors=true】
非公平锁:先尝试插队,插队失败再排队
ReentrantLock的非公平策略:NonfairSync【非公平策略:hasQueuedPredecessors=false】
多个线程能不能共享一把锁
共享锁:能
- 读锁
排他锁:不能
- 写锁
锁住同步资源失败,线程要不要阻塞?
阻塞
- Lock类
- synchronized关键字
不阻塞
- 自旋锁【CAS】
- 自适应自旋锁
sql优化
索引优化
- 尽可能低级别事务隔离
- 尽量控制事务大小,减少锁定资源量和时间长度
- 合理设计索引,尽量缩小锁的范围
- 尽可能较少检索条件,避免间隙锁
- 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁
sql优化
- EXPLAIN【SQL执行计划】
- SQL语句中IN包含的值不应过多,能用 between 就不要用 in 了
- SELECT语句务必指明字段名称,避免SELECT *增加很多不必要的消耗 ,增加了使用覆盖索引的可能性;当表结构发生改变时,前断也需要更新。
- 当只需要一条数据的时候,使用limit 1【这是为了使EXPLAIN中type列达到const类型】
- 如果排序字段没有用到索引,就尽量少排序
- 如果限制条件中其他字段没有索引,尽量少用or,or两边的字段中,如果有一个不是索引字段,会造成该查询不走索引的情况。使用 union all 或者是union(必要的时候)的方式来代替“or”
- 尽量用union all代替union,union和union all的差异主要是前者需要将结果集合并后再进行唯一性过滤操作,这就会涉及到排序,增加大量的CPU运算,加大资源消耗及延迟。当然,union all的前提条件是两个结果集没有重复数据。
- 注意范围查询语句【对于联合索引来说,如果存在范围查询,比如between,>,<等条件时,会造成后面的索引字段失效。】
- 不使用ORDER BY RAND()
- 区分in和exists,not in和not exists【IN适合于外表大而内表小的情况;EXISTS适合于外表小而内表大的情况】
- 使用合理的分页方式以提高分页的效率【limit 】
- 分段查询
- 避免在 where 子句中对字段进行 null 值判断
- 不建议使用%前缀模糊查询,例如LIKE “%name”或者LIKE “%name%”,这种查询会导致索引失效而进行全表扫描。但是可以使用LIKE “name%”。
- 避免在where子句中对字段进行表达式操作