锁
锁的定义
锁是用来协调多个线程并发访问同一共享资源时带来的安全问题,频繁用锁必然会带来性能问题,但不用锁又会造成安全问题
1)从性能上分:乐观锁和悲观锁
-
乐观锁:CAS自旋锁,是非常经典的乐观锁,并发性能比较好,但是自旋会造成很大的开销,悲观的认为当前环境下并发情况不是很很严重,任务提交时才才判断是否冲突
-
悲观锁:悲观的认为当前环境下并发情况是很严重的,所有的任务在执行时都要加上锁,保证了安全型,失去了并发性,并发性能差
乐观锁:无锁,在数据提交时才去判断是否发生冲突,version
悲观锁:每次访问数据时,都需要加锁
2)从数据库操作的类型上划分
读锁和写锁,其都是悲观锁
-
读锁(共享锁):对读取同一行数据的并发来说,是可以同时进行的,但是写不行
-
写锁(排它锁):在加写锁之后释放锁之前,其他的所有操作都不能进行
3)从数据的操作粒度上来划分
表锁和行锁
-
表锁:对整张表进行上锁
- mysiam默认支持表锁,多个线程并发操作时,一个线程操作myisam后其他所有操作都不能进行
- innodb上表锁:lock table 表1 read/write/,表2 read /write
- show open tables 查看当前会话的所有锁
- unlock tables 释放当前会话的所有锁
-
行锁:对表中的某一行记录进行上锁,支持并发读其是一个读锁
-
innodb支持行锁,在并发事务里,每个事务的读写删相当于上行锁
-
上锁的开销大,加锁速度慢但是并发性更好
-
update tb_author set name=‘m’ where id=1
-
select * from tb_author where id=1 for update
-
AQS
AQS的理解
抽象队列同步器,其是并发包中的基础组件,用来实现各种锁,ReentrantLock底层由其实现包括 state变量,等待队列,加锁线程三个核心内容
实现原理:初始时state=0,加锁线程为null,线程1加锁后利用CAS判断state递增是否成功若成功加锁队列由null变为线程1,如果此时线程2也想加锁,但是state已经被使用则其进入等待队列之中,若线程1释放锁(state递减,若state=0,则彻底释放锁加锁线程为null)则从等待队列的表头唤醒线程2,重复线程1操作,如果成功则从等待队列中出列加锁线程变为线程2
事务隔离级别
事务的隔离级别
脏读:一个事务读到另外一个事务尚未提交的数据
幻读:以相同的条件,检索以前检索过的数据,发现其他事务插入了新的数据
不可重复读:一个事务在读取后的某个时间再次去读数据发现数据改变
Read uncommit 都没解决
Read commit 只解决了脏读
repeat read 没有解决幻读
serializable 都解决,最高事务级别
CAS的理解及ABA问题
CAS(compare and sort):是乐观锁的实现方式,如果多个线程CAS更新同一个变量,只有一个线程能执行,其他线程都会失败,失败的线程可以再次尝试、有内存地址V,预期原值A,新值B,V==A则V=B否则不会执行
ABA:线程E,F同时拿到了内存地址内的变量A,但是F将变量A改成B,F执行成功E执行失败
解决:在jdk1.5后提供AtomicStampedReference解决ABA
CAS缺点:ABA问题,循环时间长,开销比较大,只能保证一个共享变量的原子操作(锁去解决),浪费cpu资源
慢sql及解决
查询时没有走索引,查询时间过长导致慢sql
解决:对查询性能进行优化
方法加锁和对象加锁区别
-
方法锁:让成员变量被访问时实现同步
-
对象锁:让实例对象之间实现同步
-
类锁:让静态资源实现同步
多用户并发怎么解决?
使用了乐观锁或者悲观锁
-
乐观锁:CAS自旋锁,是非常经典的乐观锁,并发性能比较好,但是自旋会造成很大的开销,悲观的认为当前环境下并发情况不是很很严重,任务提交时才才判断是否冲突
-
悲观锁:悲观的认为当前环境下并发情况是很严重的,所有的任务在执行时都要加上锁,保证了安全型,失去了并发性,并发性能差
乐观锁:无锁,在数据提交时才去判断是否发生冲突,version
悲观锁:每次访问数据时,都需要加锁
分段锁的理解
优点:不同段的map都能并发执行
缺点:分成很多段时,容易造成内存空间不连续或者碎片化,操作map时竞争同一个锁的概率很小,容易造成更新等操作时间过长,分段锁性能降低
sql语句使用where时尽量不要使用哪些语法?
使用where时不要使用变量或者表达式(!=,>,<,1=1等)以及in和or会导致不走索引
MVCC理解及实现原理
mvcc:多版本并发控制,是一种解决读写冲突的无所并发机制,提高并发性能,让其并发操作数据库时,读操作不阻塞写操作,写操作不阻塞读操作,解决了脏读幻读不可重复的等隔离问题
实现原理:在表中默认创建了三个字段分别代表事务id,索引,和指向日志的指针,用排它锁锁定该行记录,将记录复制到undolog日志里面然后进行事务,若成功则提交,不成功则根据指针找到日志中存储的信息
公平锁与非公平锁?
公平锁:是指多个线程按照申请锁得顺序来获取锁 ReetrantLock(boolean),true为公平锁false为非公平锁
非公平锁:是指多个线程并不是按照申请锁得顺序来获取锁,有可能后申请锁的线程优先级高于先前申请的,在高并发情况下,有可能会造成优先级反转或者饥饿现象 Synchronized
可重入锁与不可重入锁?
可重入锁(递归锁):线程可以进入任何一个他已经拥有的锁所同步着的代码块,ReentrantLock,synchronized作用:防止死锁
不可重入锁:相反
死锁:两个或者以上的线程争夺同一共享资源的僵局,如果没有外力干涉这种局面会一直保持下去直到释放资源
class lock implements Runnable {
private String lockAA;
private String lockBB;
public lock(String lockAA, String lockBB) {
this.lockAA = lockAA;
this.lockBB = lockBB;
}
@Override
public void run() {
synchronized (lockAA) {
System.out.println(Thread.currentThread().getName()+"持有"+lockAA+"尝试获取"+lockBB);
synchronized (lockBB){
System.out.println(Thread.currentThread().getName()+"持有"+lockBB+"尝试获取"+lockAA);
}
}
}
}
public class dieLock {
public static void main(String[] args) {
String lockAA = "lockA";
String lockBB = "lockB";
new Thread(new lock(lockAA, lockBB), "ThreadAAA").start();
new Thread(new lock(lockBB, lockAA), "ThreadBBB").start();
}
}
自旋锁:解决阻塞,线程AA和BB,AA获取锁后线程BB进行CAS发现预期原不是null,则陷入while循环,直到AA线程释放锁
public class SpinLockDemo {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void myLock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "==" + "come in");
while (!atomicReference.compareAndSet(null, thread)) {
}
}
public void myUnLock() {
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread, null);
System.out.println(Thread.currentThread().getName() + "==" + "come back");
}
public static void main(String[] args) {
SpinLockDemo lockDemo = new SpinLockDemo();
new Thread(() -> {
lockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
lockDemo.myUnLock();
}, "AA").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
new Thread(() -> {
lockDemo.myLock();
lockDemo.myUnLock();
}, "BB").start();
}
}
独占锁:
该锁只能被一个线程持有,synchronized和reentrantLock都是独占锁
共享锁:该锁可以被锁个线程持有
对于ReentrantReadWriteLock其读锁是共享锁,写锁是独占锁
读锁的共享锁可以保证并发读,是非常高效的
class source {
private volatile Map<String, Object> map = new HashMap<>();
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void put(String key, Object value) {
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "==正在写入==" + key);
TimeUnit.MILLISECONDS.sleep(300);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "==写入完成");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
public void get(String key) {
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "==正在读");
TimeUnit.MILLISECONDS.sleep(300);
Object value = map.get(key);
System.out.println(Thread.currentThread().getName() + "==读完成");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) {
source s = new source();
for (int i = 1; i <= 5; i++) {
final int tmp_int = i;
new Thread(() -> {
s.put(tmp_int + "", tmp_int + "");
}, String.valueOf(i)).start();
}
for (int i = 1; i <= 5; i++) {
final int tmp_int = i;
new Thread(() -> {
s.get(tmp_int + "");
}, String.valueOf(i)).start();
}
}
}