首先 Java中 synchronized 是可重入锁
为什么要锁?
如下代码 我们原本想要得到的结果是count = 10000,但是真正输出的确是不稳定的结果
出现这种情况明显是代码出BUG了 。 这里我们分析一下:
首先我们要明白这此处相加的过程中其实分为三个步骤:
1:load 从内存中读取值到寄存器中
2:add 在寄存器中数据加 1
3:save将在寄存器中相加的结果再保存到内存中
此时我们2个线程都在进行数据的相加 由于他们针对的是同一对象(即count)且彼此是相互独立的,再加上线程调度是随机的 最终就会出现如下现象:
出现这种情况最主要是一下3个原因:
1:线程调度是随机的 即 load ,add ,save 这三个操作单一线程是正常走,但从2个线程看则是完全随机的比如A进行load B则可能已经在save了。
2:这两个线程是针对同一变量在修改。
3:修改操作不是原子的,即修改操作是需要进行load,add,save 3个步骤一个一个全部完成后才算完成,如果是原子的意思就是一步就能实现这3个操作的意思。
此时我们的解决方法就是加锁。
在Java中最常用的方法就是使用synchronized关键字。
关于死锁 如果是不可重入锁 那就是当一个线程针对一个对象连续两次加锁,彼此等待就形成了死锁。
synchronized是可重入锁则不会死锁:
而换一种情况即 t1获取到锁 jocker1 ,t2获取到锁 jocker2 后,t1又想获取 jocker2,t2又想获取jocker1。双方此时都不愿意先放开锁,此时就会僵持住就会形成死锁。
而如果像 synchronized 这种可重入锁 系统在识别到是同一个线程对同一对象加锁时,就会直接加锁成功,不同对象则谁后来则谁会形成阻塞等待。这样避免了死锁的产生,可以说非常友好,同时对于加了多重锁的情况什么时候真正解锁,则其内置计数器 加一次锁计数就加一,每解锁一次计数器就减一,直到减到0的时候就真正解锁完成。
死锁的成因,主要由一下四个必要条件:
1:互斥使用:(锁本身的特性)当一个线程把锁上锁成功后 ,别的线程也想加锁则需要阻塞等待。
2: 不可抢占:(锁本身的特性)当锁被线程1拿到之后,线程2只能等线程1适当后才能加锁,无法强制抢夺过来。
3:请求保持:(代码结构)一个线程尝试获取多把锁(一个线程拿到锁1后,又想获取锁2,同时又不放开锁1)(吃这碗里,看着锅里的)
4:循环等待/环路等待: (车钥匙放家了,家门钥匙锁车里了)
如下图:
对于解决方法:
问题1和2 是本身特性无法解决,问题3的解决方法是 尽量避免在代码中使用嵌套加锁,比如1包2这种。 如无法避免即问题4这种情况 则约定好顺序统一先加小的再加大的。即如下图: