MySQL是一个客户端-服务器结构的程序
事务的四大原则ACID
-
A——(Atomicity)原子性:
把多个SQL打包成一个整体,不可分割,要么执行全部,要么不执行
-
C——(Consistency)一致性:
事务执行前与执行后,数据符合正常的逻辑的情况,如小明给小红转账100,事务执行后小明兜里少100块,小红兜里多100块
-
==💫I——(Isolation)隔离性:==
一个数据库中可以执行多个事务,并且要求使多个事务之间互相影响尽量小,故有隔离性概念
-
D——(Duration)持久性:
事务的执行的结果会保存在硬盘上
它与多线程中的锁1密切相关,其背后的逻辑是相似的
-
锁
锁的存在就是防止线程冲突的情况,一个线程得到了锁,其他的线程如果想要对同一个变量进行修改,必须要等 ==上一个得到锁的线程释放锁后==,其他的线程通过“公平竞争2”得到锁,才能往下执行,不然就只能等,进入阻塞状态
事务的背后跟锁是密切相关的
锁的特点
-
互斥 / 独占
有锁就会有线程的 ==锁竞争 / 锁冲突==
其他语言中,上锁、解锁是由 lock()与 unlock()配合,但有个缺陷,可能写到最后就忘记解锁了,导致其他线程得不到锁无法执行下去,永远阻塞等待,也叫==#线程饿死#==
在Java中为了避免这种情况发生,直接规定了synchronize(locker){ } 需要加锁的逻辑写到括号内,逻辑执行到括号外自动解锁,进入synchronized语句中自动上锁
-
locker 的对象可以是任意的,只要是Object / Object子类的实例。但必须是多个线程对同一个对象进行加锁!!!!
就像追妹子一样,多个老铁追同一个妹子才能产生锁🔒竞争的情况,要是一人各追一个,加锁就没有意义了~~毕竟线程竞争的本质是多个线程同时访问同一份共享资源产生争夺
-
如果其他的线程也尝试加锁操作就会产生阻塞,从而避免线程执行指令的过程中“被插队”
-
-
锁的粒度
锁内执行的逻辑多,粒度就大;相反代码逻辑少,粒度小
这里的代码逻辑少==不代表代码的行数==,而是==代码的执行逻辑==,注意区分🎗️
-
锁的开销
锁虽然好,但不是什么时候都需要加锁的,加锁解锁是需要消耗资源的,不能无脑加锁,加锁的代价使得程序的效率更低~~
-
可重入锁
形如这样的代码
synchronized(locker){ c.add(); } public synchronized void add(){ count++; }线程对c进行加锁,但add方法中又有一层锁,对c进行了二次加锁,这会产生问题吗??
——Java已经帮我们解决了这个问题
理论上执行到第二个加锁的地方是会堵塞住无法继续往下执行的,但Java的synchronized做了特殊处理,对可重入锁规定了——第一次加锁成功后,synchronized内部会判定第二次加锁:如果是同一个线程加锁,则会跳过;如果是不同的线程加锁,则加锁生效,阻塞效果~~
-
死锁
一个图了解死锁
- 两者都在等待对方释放锁,无限等待下去,形成了==循环依赖==的局面,造成了死锁
- 还有西方的哲学餐问题(n个线程m把锁)
平时写代码的时候很容易就写成死锁的情况,要千万注意注意再注意!!!!
死锁很重要
如何避免写出死锁的代码?我觉得首先要懂得形成死锁的原因
形成死锁的原因
只要打破其中一个就能避免死锁~~
- 锁是互斥的(锁的特性)
- 锁不可被强行抢占
以上两点是synchronized的特性,改变不了的。所以我们针对下面两个情况着重讨论
-
循环依赖请求
如上我举的例子。
-
请求和保持
如线程A获得了锁,但在锁内部中又请求了第二把锁,属于吃着碗里的 想着锅里的
与上一个循环依赖请求有密切关系,如
Thread t1 = new Thread(() -> { synchronized (locker) { System.out.println("T1线程获得了锁"); synchronized (locker2) { System.out.println("t1线程获得了第二把锁"); } } }); t1.start(); 单独运行这一个线程是没有问题的,但如果我们加多一个,同样是吃着碗里的想着锅里的线程呢?
Thread t1 = new Thread(() -> { synchronized (locker) { System.out.println("T1线程获得了锁"); synchronized (locker2) { System.out.println("t1线程获得了第二把锁"); } } }); Thread t2 = new Thread(() -> { synchronized (locker2) { System.out.println("T2线程获得了锁"); synchronized (locker) { System.out.println("t2线程获得了第二把锁"); } } }); t1.start(); t2.start(); 最后就导致死锁
如何避免死锁
- 打破等待循环——写代码的时候避免锁的嵌套
- 打破请求与保持——约定加锁的顺序,给锁进行编号,从小到大依次加锁
-
-
公平竞争
操作系统对于线程的调度是复杂的,随机的,我们感知不到,也无法干预
操作系统的开发大佬们在多线程对锁的竞争方面制定了规则
因此引入了公平竞争与不公平竞争
公平竞争vs不公平竞争
公平竞争
- 他们规定了 多个线程处于阻塞状态等待锁的期间,采取==先来后到==的方式,即最先请求锁的线程会得到锁
不公平竞争
- 则是当一个线程释放锁后,其他的线程 ==各凭本事== 争取到锁
当然,系统的调度是一个庞大复杂的系统,不同的进程会有不同的调度方式,这种锁竞争方式是灵活多变的,我们只需要知道有这个东西就可以了
↩︎