一、锁分类

  • 从性能上分为乐观锁(用版本对比来实现)和悲观锁
  • 从对数据库操作的类型分,分为读锁和写锁 (都属于悲观锁)

读锁(共享锁,S锁(Shared)):针对同一份数据,多个读操作可以同时进行而不会互相影响
写锁(排它锁,X锁(eXclusive)):当前写操作没有完成前,它会阻断其他写锁和读锁

  • 从对数据操作的粒度分,分为表锁和行锁

1.1读锁会阻塞写,写锁会阻塞读和写
1.1.1读锁与读锁互不影响。
例:读与读互不影响:
圖片.png
1.1.2写锁与写锁相斥;写锁与读锁相斥。
例:读锁会阻塞写
圖片.png
解锁:在进行下一步操作。

例:写锁会阻塞写
圖片.png
解锁:
圖片.png

1.2表锁与行锁的区别:

表锁虽然开销小,锁表快,但高并发下性能低,粒度大
行锁虽然开销大,锁表慢,锁的粒度小,发生锁冲突的概率低;处理并发的能力强

例:A事务给id=1 的这行数据加上行锁;B事务操作id=1的数据时会产生阻塞;但是不会对表中其他数据产生影响:
圖片.png

圖片.png

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

第三步:若是将col_usename的索引去掉;会出现下面的情况:
圖片.png
A事务给col_username=’a’加上行锁,B事务对其他事务进行操作时正常情况下时不会产生阻塞的;但是下图中B事务产生了阻塞,这里行锁就升级为了表锁:
圖片.png

二、死锁造成的原因

1、产生死锁的四个必要条件:
(1) 互斥条件:一个资源每次只能被一个进程专使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之
一不满足,就不会发生死锁。

哲学家算法解决死锁问题:
有五个哲学家,他们的生活方式是交替地进行思考和进餐,哲学家们共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五支筷子,平时哲学家进行思考,饥饿时便试图取其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐,该哲学家进餐完毕后,放下左右两只筷子又继续思考。

约束条件:
(1)只有拿到两只筷子时,哲学家才能吃饭。
(2)如果筷子已被别人拿走,则必须等别人吃完之后才能拿到筷子。
(3)任一哲学家在自己未拿到两只筷子吃完饭前,不会放下手中已经拿到的筷子。

根据描述实现代码如下:

  1. //哲学家在拿筷子的时候,确保左右都有筷子才同时拿起左右两只筷子(避免死锁)
  2. class Chopsticks {
  3. public static List<Boolean> chops = new ArrayList<Boolean>();
  4. static {
  5. chops.add(false); //为了方便计算,第一个不会参与计算
  6. chops.add(false);
  7. chops.add(false);
  8. chops.add(false);
  9. chops.add(false);
  10. }
  11. public synchronized void getChop() {
  12. String currentName = Thread.currentThread().getName();
  13. int index = Integer.parseInt(currentName);
  14. while (chops.get(index) || chops.get((index + 1)%5)) {
  15. try {
  16. wait();
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. }
  21. chops.set(index, true);
  22. chops.set((index + 1)%5 ,true);
  23. }
  24. public synchronized void freeChop() {
  25. String currentName = Thread.currentThread().getName();
  26. int index = Integer.parseInt(currentName);
  27. chops.set(index, false);
  28. chops.set((index + 1)%5 ,false);
  29. notifyAll();
  30. }
  31. }
  32. class PhilosopherThread extends Thread {
  33. private String name; //线程名称,给哲学家编序号用
  34. private Chopsticks chopsticks;
  35. public PhilosopherThread (String name, Chopsticks chopsticks) {
  36. super(name);
  37. // this.name = name;
  38. this.chopsticks = chopsticks;
  39. }
  40. @Override
  41. public void run() {
  42. while (true) {
  43. chopsticks.getChop();
  44. System.out.println(Chopsticks.chops);
  45. this.eat();
  46. chopsticks.freeChop();
  47. }
  48. }
  49. public void eat() {
  50. try {
  51. Thread.sleep(1000);
  52. } catch (InterruptedException e) {
  53. e.printStackTrace();
  54. }
  55. }
  56. }
  57. public class PhilosopherTest {
  58. public static void main(String[] args) {
  59. Chopsticks chopsticks = new Chopsticks();
  60. PhilosopherThread philosopherThread1 = new PhilosopherThread("0", chopsticks);
  61. PhilosopherThread philosopherThread2 = new PhilosopherThread("1", chopsticks);
  62. PhilosopherThread philosopherThread3 = new PhilosopherThread("2", chopsticks);
  63. PhilosopherThread philosopherThread4 = new PhilosopherThread("3", chopsticks);
  64. PhilosopherThread philosopherThread5 = new PhilosopherThread("4", chopsticks);
  65. philosopherThread1.start();
  66. philosopherThread2.start();
  67. philosopherThread3.start();
  68. philosopherThread4.start();
  69. philosopherThread5.start();
  70. }
  71. }

2.为什么会产生死锁
死锁问题被认为是线程/进程间切换消耗系统性能的一种极端情况。在死锁时,线程/进程间相互等待资源,而又不释放自身的资源,导致无穷无尽的等待,其结果是任务永远无法执行完成。哲学家问题便是线程资源竞争导致的死锁现象,在程序运行一段时间后,程序所处的状态是n位哲学家(n个线程)都各自获取了一只筷子的状态,此时所有哲学家都想获取第二只筷子去吃饭,但是共享资源n只筷子已经都被n位哲学家握在手里了,彼此想要的筷子都在其他哲学家手中,又没有机制能让任何哲学家放弃握在手中的筷子,从而照成了所有哲学家(所有线程)都在等待其他人手中资源的死锁问题。

3.死锁的解除与预防**
一般解决死锁的途径分为死锁的预防,避免,检测与恢复这三种。
死锁的预防是要求线程/进程申请资源时遵循某种协议,从而打破产生死锁的四个必要条件中的一个或几个,保证系统不会进入死锁状态。
死锁的避免不限制线程/进程有关申请资源的命令,而是对线程/进程所发出的每一个申请资源命令加以动态地检查,并根据检查结果决定是否进行资源分配。
死锁检测与恢复是指系统设有专门的机构,当死锁发生时,该机构能够检测到死锁发生的位置和原因,并能通过外力破坏死锁发生的必要条件,从而使得并发进程从死锁状态中恢复出来。
对于java程序来说,产生死锁时,我们可以用jvisualvm/jstack来分析程序产生的死锁原因,根治死锁问题。