其实我一直有这样一个感觉,用锁来形容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