一、锁分类
- 从性能上分为乐观锁(用版本对比来实现)和悲观锁
- 从对数据库操作的类型分,分为读锁和写锁 (都属于悲观锁)
读锁(共享锁,S锁(Shared)):针对同一份数据,多个读操作可以同时进行而不会互相影响
写锁(排它锁,X锁(eXclusive)):当前写操作没有完成前,它会阻断其他写锁和读锁
- 从对数据操作的粒度分,分为表锁和行锁
1.1读锁会阻塞写,写锁会阻塞读和写
1.1.1读锁与读锁互不影响。
例:读与读互不影响:
1.1.2写锁与写锁相斥;写锁与读锁相斥。
例:读锁会阻塞写:
解锁:在进行下一步操作。
例:写锁会阻塞写:
解锁:
1.2表锁与行锁的区别:
表锁虽然开销小,锁表快,但高并发下性能低,粒度大
行锁虽然开销大,锁表慢,锁的粒度小,发生锁冲突的概率低;处理并发的能力强
例:A事务给id=1 的这行数据加上行锁;B事务操作id=1的数据时会产生阻塞;但是不会对表中其他数据产生影响:

1.3行锁的锁是索引上的,如果这个字段没有索引则行锁升级为表锁:
例:第一步:给col_username加上索引
第二步:A事务给col_username=’a’加上行锁,B事务对col_username=’a’操作时会产生阻塞现象;但是对其他数据不产生影响;例如:

第三步:若是将col_usename的索引去掉;会出现下面的情况:
A事务给col_username=’a’加上行锁,B事务对其他事务进行操作时正常情况下时不会产生阻塞的;但是下图中B事务产生了阻塞,这里行锁就升级为了表锁:
二、死锁造成的原因
1、产生死锁的四个必要条件:
(1) 互斥条件:一个资源每次只能被一个进程专使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之
一不满足,就不会发生死锁。
哲学家算法解决死锁问题:
有五个哲学家,他们的生活方式是交替地进行思考和进餐,哲学家们共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五支筷子,平时哲学家进行思考,饥饿时便试图取其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐,该哲学家进餐完毕后,放下左右两只筷子又继续思考。
约束条件:
(1)只有拿到两只筷子时,哲学家才能吃饭。
(2)如果筷子已被别人拿走,则必须等别人吃完之后才能拿到筷子。
(3)任一哲学家在自己未拿到两只筷子吃完饭前,不会放下手中已经拿到的筷子。
根据描述实现代码如下:
//哲学家在拿筷子的时候,确保左右都有筷子才同时拿起左右两只筷子(避免死锁)class Chopsticks {public static List<Boolean> chops = new ArrayList<Boolean>();static {chops.add(false); //为了方便计算,第一个不会参与计算chops.add(false);chops.add(false);chops.add(false);chops.add(false);}public synchronized void getChop() {String currentName = Thread.currentThread().getName();int index = Integer.parseInt(currentName);while (chops.get(index) || chops.get((index + 1)%5)) {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}chops.set(index, true);chops.set((index + 1)%5 ,true);}public synchronized void freeChop() {String currentName = Thread.currentThread().getName();int index = Integer.parseInt(currentName);chops.set(index, false);chops.set((index + 1)%5 ,false);notifyAll();}}class PhilosopherThread extends Thread {private String name; //线程名称,给哲学家编序号用private Chopsticks chopsticks;public PhilosopherThread (String name, Chopsticks chopsticks) {super(name);// this.name = name;this.chopsticks = chopsticks;}@Overridepublic void run() {while (true) {chopsticks.getChop();System.out.println(Chopsticks.chops);this.eat();chopsticks.freeChop();}}public void eat() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}public class PhilosopherTest {public static void main(String[] args) {Chopsticks chopsticks = new Chopsticks();PhilosopherThread philosopherThread1 = new PhilosopherThread("0", chopsticks);PhilosopherThread philosopherThread2 = new PhilosopherThread("1", chopsticks);PhilosopherThread philosopherThread3 = new PhilosopherThread("2", chopsticks);PhilosopherThread philosopherThread4 = new PhilosopherThread("3", chopsticks);PhilosopherThread philosopherThread5 = new PhilosopherThread("4", chopsticks);philosopherThread1.start();philosopherThread2.start();philosopherThread3.start();philosopherThread4.start();philosopherThread5.start();}}
2.为什么会产生死锁
死锁问题被认为是线程/进程间切换消耗系统性能的一种极端情况。在死锁时,线程/进程间相互等待资源,而又不释放自身的资源,导致无穷无尽的等待,其结果是任务永远无法执行完成。哲学家问题便是线程资源竞争导致的死锁现象,在程序运行一段时间后,程序所处的状态是n位哲学家(n个线程)都各自获取了一只筷子的状态,此时所有哲学家都想获取第二只筷子去吃饭,但是共享资源n只筷子已经都被n位哲学家握在手里了,彼此想要的筷子都在其他哲学家手中,又没有机制能让任何哲学家放弃握在手中的筷子,从而照成了所有哲学家(所有线程)都在等待其他人手中资源的死锁问题。
3.死锁的解除与预防**
一般解决死锁的途径分为死锁的预防,避免,检测与恢复这三种。
死锁的预防是要求线程/进程申请资源时遵循某种协议,从而打破产生死锁的四个必要条件中的一个或几个,保证系统不会进入死锁状态。
死锁的避免不限制线程/进程有关申请资源的命令,而是对线程/进程所发出的每一个申请资源命令加以动态地检查,并根据检查结果决定是否进行资源分配。
死锁检测与恢复是指系统设有专门的机构,当死锁发生时,该机构能够检测到死锁发生的位置和原因,并能通过外力破坏死锁发生的必要条件,从而使得并发进程从死锁状态中恢复出来。
对于java程序来说,产生死锁时,我们可以用jvisualvm/jstack来分析程序产生的死锁原因,根治死锁问题。
