目录
-
乐观锁--认为读多写少
- 常通过
version
字段实现乐观锁,每次更新前检查版本号是否匹配。 - 在 Java 中,可以通过CAS(Compare And Swap)操作来实现乐观锁,通常由 AtomicInteger 等原子操作类提供。
优点:
- 并发较高,能够允许更多的线程同时操作资源。
缺点:
- 如果冲突较多,可能会频繁重试。
例:假设有一个银行账户,多个线程可能并发地修改账户余额。我们使用版本号来实现乐观锁,更新时检查版本号是否匹配。
-
class BankAccount { private int balance = 1000; private int version = 1; public boolean withdraw(int amount, int expectedVersion) { if (version != expectedVersion) { System.out.println("Data has been modified by another thread."); return false; // 数据已被其他线程修改 } if (balance >= amount) { balance -= amount; version++; // 更新版本号 System.out.println("Withdrawal successful. New balance: " + balance); return true; } else { System.out.println("Insufficient funds"); return false; } } }
-
悲观锁--认为读少写多
- 会锁住全部资源, 通常通过
synchronized
或ReentrantLock
等来实现锁。
缺点:
- 数据访问时直接加锁,其他线程无法访问。
- 并发较低,容易导致线程阻塞。
优点:
- 数据冲突概率较大时,保证数据一致性。
例:
class BankAccount {
private int balance = 1000;
public synchronized void withdraw(int amount) {
if (balance >= amount) {
balance -= amount;
System.out.println("Withdrawal successful. New balance: " + balance);
} else {
System.out.println("Insufficient funds");
}
}
}
-
自旋锁
自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。
Synchronized同步锁 :
- Synchronized是非公平锁。Synchronized在线程进入ContentionList时,等待的线程会先尝试自旋获取锁,如果获取不到就进入ContentionList,这明显对于已经进入队列的线程是不公平的。
- 还有一个不公平的事情就是Owner线程并不直接把锁传递给OnDeck线程,而是把锁竞争的权 利交给OnDeck,OnDeck需要重新竞争锁。这时自旋获取锁的线程还可能直接抢占OnDeck线程的锁资源。
参数:
- Wait Set:哪些调用wait方法被阻塞的线程被放置在这里;
- Contention List:竞争队列,所有请求锁的线程首先被放在这个竞争队列中;
- Entry List:Contention List中那些有资格成为候选资源的线程被移动到Entry List中;
- OnDeck:任意时刻,最多只有一个线程正在竞争锁资源,该线程被成为OnDeck;
- Owner:当前已经获取到所资源的线程被称为Owner;
- !Owner:当前释放锁的线程。
ReentrantLock:
ReentantLock继承接口Lock并实现了接口中定义的方法,他是一种可重入锁,除了能完成synchronized所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法。
Lock接口的主要方法:
- void lock(): 执行此方法时, 如果锁处于空闲状态, 当前线程将获取到锁. 相反, 如果锁已经被其他线程持有, 将禁用当前线程, 直到当前线程获取到锁.
- boolean tryLock():如果锁可用, 则获取锁, 并立即返回true, 否则返回false。
该方法和lock()的区别在于, tryLock()只是"试图"获取锁, 如果锁不可用, 不会导致当前线程被禁用, 当前线程仍然继续往下执行代码. 而lock()方法则是一定要获取到锁, 如果锁不可用, 就一直等待, 在未获得锁之前,当前线程并不继续向下执行. - void unlock():执行此方法时, 当前线程将释放持有的锁。
- Condition newCondition():条件对象,获取等待通知组件。该组件和当前的锁绑定,当前线程只有获取了锁,才能调用该组件的await()方法,而调用后,当前线程将缩放锁。