Java 中的锁
公平锁和非公平锁
简介
公平锁:是指多个线程按照申请锁的顺序来获取锁,类似排队打饭吗,先来后到。
非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁在高并发的情况下,有可能会造成优先级反转或者饥饿现象。
并发包中 ReentrantLock 的创建可以指定构造函数的 boolean 类型来得到公平锁或非公平锁,默认是非公平锁。
对于 Synchronized 而言,也是一种非公平锁。
区别
公平锁:公平锁,就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照 FIFO 的规则从队列中取到自己。
非公平锁:非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式。
可重入锁(递归锁)
简介
指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。
也即是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块。
好处就是可以避免死锁
代码实践
synchronized
public class LockDemo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "t1").start();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "t2").start();
}
}
class Phone {
public synchronized void sendSMS() throws Exception {
System.out.println(Thread.currentThread().getId() + "\t invoked sendSMS");
sendEmail();
}
public synchronized void sendEmail() throws Exception {
System.out.println(Thread.currentThread().getId() + "\t invoked sendEmail");
}
}
输出
12 invoked sendSMS
12 invoked sendEmail
13 invoked sendSMS
13 invoked sendEmail
Lock
public class LockDemo{
public static void main(String[] args) {
Phone phone = new Phone();
Thread thread1 = new Thread(phone);
Thread thread2 = new Thread(phone);
thread1.start();
thread2.start();
}
}
class Phone implements Runnable {
Lock lock = new ReentrantLock();
@Override
public void run() {
get();
}
private void get() {
lock.lock();
try {
System.out.println(Thread.currentThread().getId() + "\t invoked set");
set();
}finally {
lock.unlock();
}
}
private void set() {
lock.lock();
try {
System.out.println(Thread.currentThread().getId() + "\t invoked set");
}finally {
lock.unlock();
}
}
}
输出
12 invoked set
12 invoked set
13 invoked set
13 invoked set
自旋锁
简介
是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁
,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。
代码实践
public class SpinLockDemo {
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() + "\t invoked myUnLock()");
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.myLock();
}, "aa").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
spinLockDemo.myLock();
spinLockDemo.myUnLock();
}, "bb").start();
}
}
读锁(共享锁)/写锁(独占锁)
简介
共享锁:对 ReentrantReadWriteLock 其读锁是共享锁,其写锁是独占锁。
读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥的。
独占锁:指该锁一次只能被一个线程所持有。对 ReentrantLock 和 Synchronized 而言都是独占锁。
多个线程同时读一个资激是没有在何问划,所以为了满足并发量,读取共享资源应该可以同时进行。
如果有一个线程想去写共享资源,就不应该可有其它线程可以对该资源进行读或写。
小总结:
- 读-读能共存
- 读-可不能共存
- 写-写不能其存
代码实操
未加读写锁
class MyCache {
private volatile Map <String, Object> maps = new HashMap <>();
private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
public void put(String key, Object value) {
System.out.println(Thread.currentThread().getName() + "\t 正在写入:" + key);
try {
TimeUnit.MICROSECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
maps.put(key, value);
System.out.println(Thread.currentThread().getName() + "\t 完成写入:");
}
public void get(String key) {
System.out.println(Thread.currentThread().getName() + "\t 正在读取:");
try {
TimeUnit.MICROSECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object value = maps.get(key);
System.out.println(Thread.currentThread().getName() + "\t 完成读取:" + value);
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 0; i < 5; i++) {
int finalI = i;
new Thread(() -> {
myCache.put(finalI + "", finalI);
}, String.valueOf(i)).start();
}
for (int i = 0; i < 5; i++) {
int finalI = i;
new Thread(() -> {
myCache.get(finalI + "");
}, String.valueOf(i)).start();
}
}
}
输出
0 正在写入:0
1 正在写入:1
4 正在写入:4
2 正在写入:2
3 正在写入:3
0 正在读取:
1 正在读取:
2 正在读取:
3 正在读取:
4 正在读取:
4 完成写入:
1 完成写入:
2 完成读取:2
1 完成读取:null
0 完成读取:null
3 完成写入:
4 完成读取:null
3 完成读取:null
0 完成写入:
2 完成写入:
加上读写锁
class MyCache {
private volatile Map <String, Object> maps = new HashMap <>();
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 {
TimeUnit.MICROSECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
maps.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.MICROSECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object value = maps.get(key);
System.out.println(Thread.currentThread().getName() + "\t 完成读取:" + value);
} catch (Exception e) {
e.printStackTrace();
} finally {
rwLock.readLock().unlock();
}
}
}
输出
1 正在写入:1
1 完成写入:
2 正在写入:2
2 完成写入:
0 正在写入:0
0 完成写入:
4 正在写入:4
4 完成写入:
3 正在写入:3
3 完成写入:
0 正在读取:
1 正在读取:
2 正在读取:
3 正在读取:
4 正在读取:
2 完成读取:2
1 完成读取:1
0 完成读取:0
4 完成读取:4
3 完成读取:3
本文作者:李小龙他哥
本文链接:https://www.cnblogs.com/lhnstart/p/16006550.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步