(锁) 系列篇
锁的概念,更多源于生活。每家每户都有一把锁,只有持有钥匙打开锁才能进入房屋,以此来防止盗贼进入家中。
1、为什么需要锁? |
引申到软件世界高并发场景,锁即为竞争资源。只有拿到锁才能访问资源,否则进入锁等待区,以此保护资源顺序性访问防止乱序访问。
2、Java对象锁 |
Java每个对象都是Object的子类都有且只有一个锁,言外之意每个对象都可作为互斥资源来实现有序访问。以32位JVM对象头为例,根据锁状态(后续补充)具有不同标志位
(图片来自《Java并发编程的艺术》)
对象锁有两个等待池:
- Entry Set池:等待获锁池,当锁被释放之后有机会竞争获锁;
- Wait Set池:等待挂起池,调用wait之后进入该池,不能直接竞争获锁。只有当锁对象调用notify、notifyAll 唤醒后进入Entry Set池,才可竞争获锁;
/** * Class {@code Object} is the root of the class hierarchy. * Every class has {@code Object} as a superclass. All objects, * including arrays, implement the methods of this class. * * @author unascribed * @see java.lang.Class * @since JDK1.0 */ public class Object { //........ // 锁基本操作方法:获取锁、释放锁 public final native void notify(); public final native void notifyAll(); public final native void wait(long timeout) throws InterruptedException; public final void wait(long timeout, int nanos) throws InterruptedException { if (timeout < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos > 0) { timeout++; } wait(timeout); } public final void wait() throws InterruptedException { wait(0); } }
经常会被问到wait、notify方法为什么不放在Thread类里面,而是在Object类中?
实质是wait、notify都属于锁本身操作而非线程操作。每个对象都有且只有一个锁,定义在Object类中每个子类都将继承这些基本的操作,实现代码复用。
3、分布式锁 |
前面引申的锁概念都发生进程内,而在分布式环境的资源竞争就起不到作用。例如秒杀服务大多都是分布式部署,库存为100而参与秒杀用户为1w,如何保证不超卖和少卖呢?
此时需要用到分布式锁将资源状态外置,保证资源一致性。比如常用Mysql行锁或者表锁进行控制,将锁的实现细节丢给数据库,保证库存更新的顺序性。
然而锁往往会成为数据库负担,可引入redis、zookeeper分布式锁缓解数据库压力。需要注意的是大并发场景其实不适合使用锁,这里只是做个引申示例。