目录
一、读写锁(ReentrantReadWriteLock)
二、非公平锁(synchronized/ReentrantLock)
三、可重入锁/递归锁(synchronized/ReentrantLock)
四、自旋锁(spinlock)
五、乐观锁/悲观锁
六、死锁
1、死锁代码
2、死锁的检测(jps -l 与 jstack 进程号)
七、sychronized-wait-notify 与 lock-await-signal的对比
1、sychronized与lock的对比
2、分组加锁的实例
本文通过学习:周阳老师-尚硅谷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;//finalnew Thread(() -> {myCache.put(tempInt + "", tempInt + "");}, String.valueOf(i)).start();}for (int i = 1; i <= 5; i++) {//5个线程读final int tempInt = i;//finalnew 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();@Overridepublic 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变成nullatomicReference.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;}@Overridepublic 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,即存在一个死锁
七、sychronized-wait-notify 与 lock-await-signal的对比
sychronized - wait - notify |
lock - await - signal |
1、sychronized与lock的对比
sychronized | lock | |
1.定义 | JVM层面的java关键字,底层是通过monitor对象来完成 | api层面的锁,底层是JUC锁(java.util.concurrent.locks.Lock) |
2.使用方法 | 不需要用户去手动释放锁,系统自动释放 | 需要用户去手动释放锁,若没有主动释放锁,就有可能出现死锁的现象,需要lock() 和 unlock() 配置try catch语句来完成 |
3.等待是否中断 | 不可中断 | 可中断,可以设置超时方法
|
4.加锁是否公平 | 非公平锁 | 默认非公平锁,构造函数可以传递boolean值,true为公平锁,false为非公平锁 |
锁绑定多个条件Condition | 没有,要么随机,要么全部唤醒 | 可以精确唤醒 |
2、分组加锁的实例
题目:多线程之间按顺序调用,实现 A-> B -> C 三个线程启动,要求如下:
AA打印5次,BB打印10次,CC打印15次
紧接着
AA打印5次,BB打印10次,CC打印15次
..
来10轮
分析:链式唤醒的操作,适合用lock
class ShareResource {private int number = 1;//A=1,B=2,c=3private Lock lock = new ReentrantLock();//可重入锁// 这三个相当于备用钥匙private Condition condition1 = lock.newCondition();private Condition condition2 = lock.newCondition();private Condition condition3 = lock.newCondition();public void print5() {lock.lock();try {//step1、判断while(number != 1) condition1.await();//step2、干活for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + "\t " + number + "\t" + i);}// step3、唤醒,通知B线程执行number = 2;condition2.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}public void print10() {lock.lock();try {//step1、判断while(number != 2) condition2.await();//step2、干活for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + "\t " + number + "\t" + i);}//step3、唤醒,通知C线程执行number = 3;condition3.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}public void print15() {lock.lock();try {//step1、判断while(number != 3) condition3.await();//step2、干活for (int i = 0; i < 15; i++) {System.out.println(Thread.currentThread().getName() + "\t " + number + "\t" + i);}//step3、唤醒,通知A线程执行number = 1;condition1.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}
}public class SyncAndReentrantLockDemo {public static void main(String[] args) {ShareResource shareResource = new ShareResource();new Thread(() -> {for (int i = 0; i < 10; i++) {shareResource.print5();}}, "A").start();new Thread(() -> {for (int i = 0; i < 10; i++) {shareResource.print10();}}, "B").start();new Thread(() -> {for (int i = 0; i < 10; i++) {shareResource.print15();}}, "C").start();}
}