5. JAVA大厂面试第二季-java锁
作者:互联网
前言
其实我一直有这样一个感觉,用锁来形容lock其实是不恰当的,因为刻板印象里锁是用来锁住东西的,而一个线程获得了锁表示这个线程可以开始执行业务了,而并不是锁住了,停滞住了,所以我更愿意把java中的锁理解为钥匙,一个线程拿到了钥匙,于是乎一个线程就可以开始运行了。
公平锁和非公平锁
公平锁:先来后到,多个线程按照申请锁的顺序来。
非公平锁:允许插队,一上来就直接尝试占有锁,如果尝试失败就采用类似公平锁的那种方式,有可能后申请的线程比先申请的线程先获取锁,在高并发情况下,有可能会造成优先级反转或者饥饿现象,ReentrantLock默认情况是非公平锁,Synchronized也是非公平锁。
可重入锁(递归锁):指的是同一线程外层获得锁之后,内层递归函数依然可以获取该锁的代码,在同一个线程在外层获取锁的时候,进入内层方法会自动获取锁,所有的锁都应该设计为可重入的,这样可以避免死锁问题。类似于拿到大门钥匙就可以打开房子里其他所有的门。、
/**
* @ Author wuyimin
* @ Date 2021/9/7-20:11
* @ Description
*/
public class LockTest {
public static void main(String[] args) {
openReentrantLockDoor openDoor = new openReentrantLockDoor();
new Thread(() -> {
openDoor.openBigDoor();
}, "A").start();
new Thread(() -> {
openDoor.openBigDoor();
}, "B").start();
}
}
class openReentrantLockDoor{
//两个不同的锁也可以打开
ReentrantLock lock=new ReentrantLock();
ReentrantLock lock1=new ReentrantLock();
public void openBigDoor() {
lock.lock();//只要锁配对,两对以上的lock()和unlock()也成立
lock.lock();
try{
System.out.println(Thread.currentThread().getName()+"打开了大门");
openBedroom();
openBathRoom();
}catch (Exception e)
{ e.printStackTrace();
}finally {
lock.unlock();
lock.unlock();
}
}
public void openBedroom() {
lock1.lock();
try{
System.out.println(Thread.currentThread().getName()+"打开卧室门");
}catch (Exception e)
{ e.printStackTrace();
}finally {
lock1.unlock();
}
}
public synchronized void openBathRoom() {
System.out.println(Thread.currentThread().getName()+"打开浴室门");
}
}
运行结果
A打开了大门
A打开卧室门
A打开浴室门
B打开了大门
B打开卧室门
B打开浴室门
自旋锁
尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU,手写一个自旋锁试试:
/**
* @ Author wuyimin
* @ Date 2021/9/7-21:04
* @ Description 自旋锁--》自旋的本质就是while+compare方法
*/
public class SelfLock {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void myLock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "锁住");
while (!atomicReference.compareAndSet(null, thread)) {
}
}
public void myUnLock() {
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread, null);
System.out.println(Thread.currentThread().getName()+"解锁");
}
public static void main(String[] args) {
SelfLock selfLock = new SelfLock();
new Thread(() -> {
selfLock.myLock();
System.out.println("线程A执行业务中");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
selfLock.myUnLock();
}
}, "A").start();
//保证A线程先启动
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
selfLock.myLock();
System.out.println("线程B执行业务中");
selfLock.myUnLock();
}, "B").start();
}
}
代码运行结果:
A锁住
线程A执行业务中
B锁住
A解锁
线程B执行业务中
B解锁
独占锁(写锁)/共享锁(读锁)/互斥锁
独占锁:指该锁只能被一个线程所持有。ReentrantLock和Synchronized都是独占锁。
共享锁:指该锁可以被多个线程所持有。
对于ReentrantReadWriteLock来说其读锁是共享锁,写锁是独占锁。
读读是共享的,读写,写读,写写的过程是互斥的(可以理解为只要有写就是互斥的)
不加锁的情况:
public class ReadWriteLock {
public static void main(String[] args) {
MyData myData = new MyData();
//写的过程
for (int i = 0; i <5; i++) {
final int temp=i;
new Thread(() -> {
myData.put(temp+"",temp);
}, "" + i).start();
}
//读的过程
for (int i = 0; i <5; i++) {
final int temp=i;
new Thread(() -> {
myData.get(temp+"");
}, "" + i).start();
}
}
}
//模拟资源类
class MyData {
private volatile Map<String, Object> map = new HashMap<>();
public void put(String key, Object value) {
System.out.println(Thread.currentThread().getName() + "正在写入。。。" + key);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入完成");
}
public void get(String key) {
System.out.println(Thread.currentThread().getName() + "正在读取");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取完成" + o);
}
}
运行结果:
0正在写入。。。0
4正在写入。。。4
3正在写入。。。3
2正在写入。。。2
1正在写入。。。1
0正在读取
1正在读取
2正在读取
4正在读取
3正在读取
4写入完成
3写入完成
0写入完成
3读取完成3
4读取完成null
1写入完成
1读取完成null
2读取完成null
0读取完成null
2写入完成
加锁后
public class ReadWriteLock {
public static void main(String[] args) {
MyData myData = new MyData();
//写的过程
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(() -> {
myData.put(temp + "", temp);
}, "" + i).start();
}
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(() -> {
myData.get(temp + "");
}, "" + i).start();
}
}
}
//模拟资源类
class MyData {
private volatile Map<String, Object> map = new HashMap<>();//保证可见性
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void put(String key, Object value) {
lock.writeLock().lock();//写锁
try {
System.out.println(Thread.currentThread().getName() + "正在写入。。。" + key);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
public void get(String key) {
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "正在读取");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取完成" + o);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
}
运行结果:
0正在写入。。。0
0写入完成
2正在写入。。。2
2写入完成
1正在写入。。。1
1写入完成
3正在写入。。。3
3写入完成
4正在写入。。。4
4写入完成
2正在读取
0正在读取
1正在读取
3正在读取
4正在读取
1读取完成1
3读取完成3
2读取完成2
0读取完成0
4读取完成4
Process finished with exit code 0
标签:java,读取,Thread,lock,写入,线程,第二季,JAVA,public 来源: https://blog.csdn.net/weixin_51211461/article/details/120165547