锁:Sychronized、Lock
锁是用来在多线程并发阶段保障数据同步的重要手段,防止出现脏数据,加锁代码在某个时间点只能由一个线程运行,其他线程等待。
普遍熟知的锁是synchronized关键字和Lock类。
一、synchronized关键字
这个在同步中是最常用的,分成对象锁和类锁,可以对方法和代码块进行加锁。
1、对象锁,锁住的是对象,用于 synchronized(this){} synchronized(Object){} 2、类锁,锁住的是该类的所有实例 synchronized(Object.class){}
synchronized锁是在字节码级别上的锁,可以用javap(java自带的反编译工具)查看
例如查看这一段代码的字节码指令
javap执行结果如下:
可以看到是由monitorenter和monitorexit指令来实现加锁的,出现两次的monitorexit是为了确保解锁完成。
二、Lock
Lock是接口,有以下几个方法
1、获取锁 lock() 2、获取锁,除非线程中断 lockInterruptibly() 3、尝试获取锁,在锁可用获取锁 tryLock() 4、解锁 unlock() 5、返回该Lock的condition newCondition()
Lock是需要配合Condition使用的,其有await和signal方法,类似于Object的wait和notify方法。
主要看一下Lock的实现类ReentrantLock。
(1)ReentrantLock默认是非公平锁(获取锁的顺序不是按照申请顺序来)
public ReentrantLock() { sync = new NonfairSync();}
也可以用参数指定是公平锁OR非公平锁
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync();}
FairSync和NonFairSync是继承Sync的,而Sync是继承自AbstractQueuedSynchronizer(简称AQS),AQS里面维护了锁的当前状态和线程等待队列。
(2)底层加锁方法
每个资源都有一个状态字段
private volatile int state;
1、非公平锁
final void lock() { 1、当资源状态为0的时候,更改为1 if (compareAndSetState(0, 1)) 2、当前线程获取锁 setExclusiveOwnerThread(Thread.currentThread()); else 3、资源状态不为0的时候 acquire(1); }
状态为0时,将拥有锁的线程设置为当前线程
void setExclusiveOwnerThread(Thread thread) { exclusiveOwnerThread = thread;}
状态非0时,尝试获取锁
void acquire(int arg) { if (!tryAcquire(arg) && //addWaiter是往线程等待队列中新增一个节点 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //中断当前线程 selfInterrupt(); }
tryAcquire方法指向的就是nonfairTryAcquire方法:
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); 1、获取锁状态 int c = getState(); if (c == 0) { 2、当资源状态为0的时候,更改为1,当前线程获取锁 if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } 3、当前线程为拥有锁的线程 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); 4、修改锁状态 setState(nextc); return true; } return false; }
addWaiter方法是在AQS里面实现的,往线程等待队列尾部新增一个节点。
而acquireQueued方法作用是阻塞线程,重试获取锁,死循环,直至获取锁。
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { 1、获取前一个节点 final Node p = node.predecessor(); 2、如果是头结点并且获取了锁 if (p == head && tryAcquire(arg)) { 3、将获取到锁的节点设置为头结点 setHead(node); p.next = null; // help GC failed = false; return interrupted; } 4、shouldParkAfterFailedAcquire是获取 锁失败后检查上一个节点的状态,判断是否 要阻塞当前线程 if (shouldParkAfterFailedAcquire(p, node) && 5、调用线程阻塞,判断是否已中断 parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
parkAndCheckInterrupt方法:
private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); } 阻塞当前线程 public static void park(Object blocker) { Thread t = Thread.currentThread(); setBlocker(t, blocker); UNSAFE.park(false, 0L); setBlocker(t, null); }
可以看出,最终是调用Unsafe的park方法实现加锁的。
2、公平锁
公平锁和非公平锁的区别只有一个,就是在tryAcquire方法中多了一个hasQueuedPredecessors方法。
boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); 1、获取锁状态 int c = getState(); if (c == 0) { 2、判断当前线程在等待队列中是否是第二个结点, 也就是等待时间最长的节点 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } 3、当前线程已经拥有锁 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
hasQueuedPredecessors方法,判断当前线程是否是第二个节点,也就是等待时间最长的节点
boolean hasQueuedPredecessors() { Node t = tail; Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }
(3)释放锁
1、unlock方法
public void unlock() { sync.release(1);}
2、release方法
boolean release(int arg) { 1、释放锁,也就是修改锁状态 if (tryRelease(arg)) { Node h = head; 2、释放完成后,唤醒下一个等待线程。 if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; } boolean tryRelease(int releases) { 1、获取剩余状态 int c = getState() - releases; 2、判断当前线程是否拥有锁 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; 3、只有当锁状态为0时,才是完全释放 if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; }
第3步是因为同一个线程多次调用lock时,锁状态都会相应的新增,所以可以从这里看出,如果多次调用Lock进行加锁,在解锁的时候也要多次调用unLock方法。
三、Synchronized和Lock的区别
(1)synchronized是关键字,Lock是类,类拥有更大的自由度。
(2)synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁。
(3)synchronized会自动释放锁,Lock需要执行unLock方法。
(4)synchronized是非公平的,Lock两者都可。
(5)Lock可以通过某个condition精确唤醒某个线程。
ok,以上就是synchronized和Lock的粗略分析。
=======================================================
我是Liusy,一个喜欢健身的程序员。
提前祝大家伙中秋国庆快乐!!!
欢迎关注微信公众号【Liusy01】,一起交流Java技术及健身,获取更多干货。