多用例子测试,写一下,很好理解。
synchronized和ReentrantLock都是什么?
可参考
https://stackoverflow.com/questions/11821801/why-use-a-reentrantlock-if-one-can-use-synchronizedthis
https://juejin.im/post/5aeb0a8b518825673a2066f0
https://juejin.im/post/594a24defe88c2006aa01f1c
这是一个线程不安全的例子,因为每个线程都在公用一个state,并且没有锁
public class ThreadSafeSimple { public int sharedState; public void noSafeAction() { while (sharedState < 10000) { int former = sharedState++; int latter = sharedState; if (former != latter - 1) { System.out.println("former " + former + " latter " + latter); } } } public static void main(String[] args) { ThreadSafeSimple simple = new ThreadSafeSimple(); Thread threadA = new Thread(() -> simple.noSafeAction()); Thread threadB = new Thread(() -> simple.noSafeAction()); threadA.start(); threadB.start(); } }
加上一个方法锁,也叫同步代码块;还有一种在非静态方法上加synchronized。显然高并发并不合适
public void noSafeAction() { while (sharedState < 10000) { synchronized (this) { int former = sharedState++; int latter = sharedState; if (former != latter - 1) { System.out.println("former " + former + " latter " + latter); } } } }
再来看看ReentrantLock。什么是再入?它是表示当一个线程试图获取一个它已经获取的锁时,这个获取动作就自动成功,这是对锁获取粒度的一个概念,也就是锁的持 有是以线程为单位而不是基于调用次数。Java锁实现强调再入性是为了和pthread的行为进行区分。
再入锁可以设置公平性(fairness),我们可在创建再入锁时选择是否是公平的。
ReentrantLock fairLock = new ReentrantLock(true);
这里所谓的公平性是指在竞争场景中,当公平性为真时,会倾向于将锁赋予等待时间最久的线程。公平性是减少线程“饥饿”(个别线程长期等待锁,但始终无法获取)情况发生的一 个办法。
如果使用synchronized,我们根本无法进行公平性的选择,其永远是不公平的,这也是主流操作系统线程调度的选择。通用场景中,公平性未必有想象中的那么重要,Java默认的调 度策略很少会导致 “饥饿”发生。与此同时,若要保证公平性则会引入额外开销,自然会导致一定的吞吐量下降。所以,我建议只有当你的程序确实有公平性需要的时候,才有必要指 定它。
什么叫可重入,举个例子
Lock lock = new ReentrantLock(); long f(int n) throws InterruptedException { lock.lock(); try { if (n <= 1) return 1; return f(n - 1) + 1; } finally { lock.unlock(); } }
The reentrantlock will return 2, if you call f(2)
实现原理
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); //1. 如果该锁未被任何线程占有,该锁能被当前线程获取 if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } //2.若被占有,检查占有线程是否是当前线程 else if (current == getExclusiveOwnerThread()) { // 3. 再次获取,计数加一 int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
释放锁
protected final boolean tryRelease(int releases) { //1. 同步状态减1 int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { //2. 只有当同步状态为0时,锁成功被释放,返回true free = true; setExclusiveOwnerThread(null); } // 3. 锁未被完全释放,返回false setState(c); return free; }
如何实现公平锁
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } 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(),这个主要是避免刚释放锁的线程又取到,for公平。如果强调公平锁,那就不可避免的必须频繁切换线程,上下分切换很费时间的,所以不如在逻辑上控制。
synchronized实例测试
synchronized(this|object) {} 代码块和 synchronized 修饰非静态方法获取的锁是同一个锁,即该类的对象的对象锁。
两个线程访问不同对象的 synchronized(this|object) {} 代码块和 synchronized 修饰非静态方法是异步的,同一个类的不同对象的对象锁互不干扰。
在同一对象的情况下,synchronized(类.class) {} 代码块或 synchronized 修饰静态方法和 synchronized(this|object) {} 代码块和 synchronized 修饰非静态方法的行为一致。