目录
一、读写锁(ReentrantReadWriteLock)
二、非公平锁(synchronized/ReentrantLock)
三、可重入锁/递归锁(synchronized/ReentrantLock)
四、自旋锁(spinlock)
五、乐观锁/悲观锁
六、死锁
1、死锁代码
2、死锁的检测(jps -l 与 jstack 进程号)
本文通过学习:周阳老师-尚硅谷Java大厂面试题第二季 总结的锁相关的笔记
一、读写锁(ReentrantReadWriteLock)
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();//volatile保证可见性
private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
public void put(String key, Object value) {
rwLock.writeLock().lock();//写锁创建
try {
System.out.println(Thread.currentThread().getName() + "\t 正在写入:" + key);
try {
// 模拟网络拥堵,延迟0.3秒
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "\t 写入完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
rwLock.writeLock().unlock();//写锁释放
}
}
public void get(String key) {
rwLock.readLock().lock();//读锁创建
try {
System.out.println(Thread.currentThread().getName() + "\t 正在读取:");
try {
TimeUnit.MILLISECONDS.sleep(300);//模拟网络拥堵,延迟0.3秒
} catch (InterruptedException e) {
e.printStackTrace();
}
Object value = map.get(key);
System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + value);
} catch (Exception e) {
e.printStackTrace();
} finally {
rwLock.readLock().unlock();//读锁释放
}
}
public void clean() {
map.clear();//清空缓存
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 1; i <= 5; i++) {//5个线程写
final int tempInt = i;//final
new Thread(() -> {
myCache.put(tempInt + "", tempInt + "");
}, String.valueOf(i)).start();
}
for (int i = 1; i <= 5; i++) {//5个线程读
final int tempInt = i;//final
new Thread(() -> {
myCache.get(tempInt + "");
}, String.valueOf(i)).start();
}
}
}
二、非公平锁(synchronized/ReentrantLock)
定义 | 区别 | |
非公平锁 | 多个线程获取锁的顺序,并不是按照申请锁的顺序,有可能申请的线程比先申请的线程优先获取锁,在高并发环境下,有可能造成优先级翻转,或者饥饿的线程(也就是某个线程一直得不到锁) | 比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式 |
公平锁 | 多个线程按照申请锁的顺序来获取锁,类似于排队买饭,先来后到,先来先服务,就是公平的,也就是队列 | 很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列中的第一个,就占用锁,否者就会加入到等待队列中,以后安装FIFO的规则从队列中取到自己 |
- synchronized是非公平锁
- ReentrantLock默认非公平锁
- Lock lock = new ReentrantLock(true);//默认false非公平锁,true公平锁
三、可重入锁/递归锁(synchronized/ReentrantLock)
synchronized可重入锁 | ReentrantLock可重入锁 |
class MySynchronized {
public synchronized void sendSMS() throws Exception{//发短信
System.out.println(Thread.currentThread().getName() + "\t invoked sendSMS()");
sendEmail();//同步方法中调用另外一个同步方法
}
public synchronized void sendEmail() throws Exception{//发邮件
System.out.println(Thread.currentThread().getId() + "\t invoked sendEmail()");
}
}
public class MyDemo {
public static void main(String[] args) {
MySynchronized mySynchronized = new MySynchronized();
new Thread(() -> {
try {
mySynchronized.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "t1").start();
new Thread(() -> {
try {
mySynchronized.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "t2").start();
}
}
/**
t1 invoked sendSMS()
t1 invoked sendEmail()
t2 invoked sendSMS()
t2 invoked sendEmail()
*/
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class MyReentrantLock implements Runnable{
Lock lock = new ReentrantLock();
@Override
public void run() {
method1();
}
public void method1() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName()
+ "\t exe method1");
method2();
} finally {
lock.unlock();
}
}
public void method2() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName()
+ "\t exe method2");
} finally {
lock.unlock();
}
}
}
public class ReenterLockDemo {
public static void main(String[] args) {
MyReentrantLock myReentrantLock = new MyReentrantLock();
Thread t1 = new Thread(myReentrantLock, "t1");
Thread t2 = new Thread(myReentrantLock, "t2");
t1.start();
t2.start();
}
}
/**
t1 exe method1
t1 exe method2
t2 exe method1
t2 exe method2
*/
四、自旋锁(spinlock)
public class SpinLockDemo {
// 现在的泛型装的是Thread,原子引用线程
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void myLock() {//加锁
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "\t come in ");
//开始自旋,期望值是null,更新值是当前线程,如果是null,则更新为当前线程,否者自旋
while(!atomicReference.compareAndSet(null, thread)) {
}
}
public void myUnLock() {//解锁
Thread thread = Thread.currentThread();
//自己用完了后,把atomicReference变成null
atomicReference.compareAndSet(thread, null);
System.out.println(Thread.currentThread().getName() + "\t invoked myUnlock()");
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(() -> {
spinLockDemo.myLock();//加锁
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.myUnLock();//释放锁
}, "t1").start();
//1秒后,启动t2线程占用锁
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
spinLockDemo.myLock();//加锁
spinLockDemo.myUnLock();//释放锁
}, "t2").start();
}
}
/**
t1 come in
.....五秒后.....
t1 invoked myUnlock()
t2 come in
t2 invoked myUnlock()
*/
首先输出的是 t1 come in,然后1秒后,t2线程启动,发现锁被t1占有,然后不断执行compareAndSet方法,来进行比较,直到t1释放锁后,也就是5秒后,t2成功获取到锁,然后释放
五、乐观锁/悲观锁
1、MybatisPlus使用乐观锁的3步走
step1、在数据库增加version字段,默认为1
step2、在实体类增加对应的字段
@Version
private Integer version;
step3、注册乐观锁,在MybatisPlusConfig中配置
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
2、悲观锁
六、死锁
1、死锁代码
import java.util.concurrent.TimeUnit;
class HoldLockThread implements Runnable{
private String lockA;
private String lockB;
public HoldLockThread(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName()
+ "\t 自己持有" + lockA + "\t 尝试获取:" + lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB) {
System.out.println(Thread.currentThread().getName()
+ "\t 自己持有" + lockB + "\t 尝试获取:" + lockA);
}
}
}
}
public class DeadLockDemo {
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new HoldLockThread(lockA, lockB), "t1").start();
new Thread(new HoldLockThread(lockB, lockA), "t2").start();
}
}
/**
t1 自己持有lockA 尝试获取:lockB
t2 自己持有lockB 尝试获取:lockA
*/
2、死锁的检测(jps -l 与 jstack 进程号)
step1、jps -l
step2、jstack 7560 #后面参数是jps输出的该类的pid
查看最后一行,我们看到 Found 1 deadlock,即存在一个死锁