一、可重入锁
可重入锁又叫递归锁,它的定义也很简单,就像它的字面意思一样,支持重新进入的锁,即:同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。如下代码所示:
public class synchronizedLock{
public static void main(String[] args) {
/**
* 在调用方法sms的时候,phone对象上锁,同时方法内部也调用了call方法,
* 此时call方法还是本身的锁,如果不是重入锁的话,得等待sms方法释放锁,
* 可是sms方法内部又调用了call方法,call方法没得到锁没执行完,
* sms方法又不会释放锁,因此如果不是重入锁的话,就会造成死锁现象在这里
* 重入锁用最通俗的话来说就是:
* 你拿着你家大门的钥匙,你开了你家大门的锁,你就可以随意进你家各个房间的门,
* */
Phone phone = new Phone();
new Thread(() -> {
phone.sms();
}, "A").start();
new Thread(() -> {
phone.sms();
}, "B").start();
}
}
/**
* 在方法上加锁,锁的是synchronized锁住的方法的调用者
* 因此只有一个锁
* */
class Phone {
public synchronized void sms() {
System.out.println(Thread.currentThread().getName() + "=>sms");
call(); // 这里也有锁
}
public synchronized void call() {
System.out.println(Thread.currentThread().getName() + "=>call");
}
}
常用的可重入锁:
synchronized
ReentrantLock
二、公平锁/非公平锁
1、公平锁:简单来说就是讲究先来后到,先来的线程先获得锁
2、非公平锁:简单来说就是不讲道理,不管你先来还是后来,谁抢到就是谁的,允许插队。
ReentrantLock :
我们可以看到ReentrantLock的参数默认事false的,意味着它是非公平锁。
常见的非公平锁:
synchronized
ReentrantLock
三、独享锁(互斥锁)/共享锁(读写锁)
独享锁:一个锁只能被一个线程所持有—ReentrantLock,synchronized ,ReentrantReadWriteLock.writeLock
共享锁:一个锁能同时被多个线程所持有—ReentrantReadWriteLock.readLock
四、乐观锁/悲观锁
乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。
悲观锁就如同它的名字一样,是一种比较悲观的思想,他对数据被外界修改持有保守态度,认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此无论如何在操作资源的时候只允许一个线程获得它的锁进行修改,其他只能阻塞等待。常用于写的操作。其思想总结一句话就是“先取锁再访问”。如下图所示
缺点是由于一次资源的操作只允许一个线程执行,其他线程只能等待,会造成效率底下的问题。处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会。
乐观锁如同他名字一样,是一种乐观的思想,他认为数据在一般情况下是不会发生问题的,因此它只在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测。如果发现冲突则进行事务的回退。如下图所示:
优点:常用于读的操作,可以避免死锁现象的发生。
五、自旋锁
我们在刚才探究CAS底层的时候其实已经见过自旋锁,自旋锁他会不断的尝试直到成功为止,可以理解为就是一个do-while循环,正如它的名字那样。
自旋锁测试:
自定义锁类
//自定义锁
public class spinLock {
//AtomicReference参数是引用类型默认值为null
//AtomicReference参数是int默认值为0
AtomicReference<Thread> atomicReference = new AtomicReference<>();//此时默认值是null
//加锁
public void myLock() {
Thread thread = Thread.currentThread();
System.out.println(thread.currentThread().getName()+"=>加锁中");
//自旋锁关键--false死循环
while (!atomicReference.compareAndSet(null, thread)) {
}
}
//解锁
public void myUnLock() {
Thread thread = Thread.currentThread();
System.out.println(thread.currentThread().getName()+"=>解锁中");
atomicReference.compareAndSet(thread,null);
}
}
测试类:
public class spinLockTest {
public static void main(String[] args) throws InterruptedException {
//使用我们自定义锁
spinLock lock = new spinLock();
new Thread(() -> {
//触发加锁,输出T1=>加锁中
lock.myLock();
try {
TimeUnit.SECONDS.sleep(4);
} catch (Exception e) {
e.printStackTrace();
} finally {
//触发解锁,输出T1=>解锁中
lock.myUnLock();
}
}, "T1").start();
//等待两秒的意思是让T2线程拿到锁输出T2=>加锁中
TimeUnit.SECONDS.sleep(2);
new Thread(() -> {
lock.myLock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.myUnLock();
}
}, "T2").start();
}
}
六、死锁
简单来说就是两个互相持有锁的线程,都想去获取对方的锁,而谁也不让谁从而导致死锁,直接上代码:
public class DeadLock {
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new MyThread(lockA, lockB), "T1").start();
new Thread(new MyThread(lockB, lockA), "T2").start();
}
}
class MyThread implements Runnable {
private String lockA;
private String lockB;
public MyThread(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + "lock:" + lockA + "=>get" + lockB);
try {
//保证第二个线程也能先拿到自己的锁
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//尝试获取对方的锁
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + "lock:" + lockB + "=>get" + lockA);
}
}
}
}
结果展示 线程T1拿到了lockA的锁尝试获取B的锁,线程T2拿到了lockB的锁尝试获取A的锁
那么我们怎么解决排查到死锁呢?
第一步、通过在控制台输入jps查看所有线程如图所示:
第二步、输入jstack 进程号查看对应的线程,往下翻到错误处
第三步、找到死锁
关于锁先总结到这里,下次有空再补充!