我们将围绕卖票案例,用各种锁去解决卖票重复和溢出等情况
public class SellDemo {
/**
* 库存
*/
static int ticketSum = 100;
/**
* 卖票
*/
public static void sell() {
for (int i = 0; i < 100; i++) {
try {
if (ticketSum > 0) {
// 模拟业务处理时间
Thread.sleep(10);
ticketSum--;
System.out.println(Thread.currentThread().getName() + "卖票成功,当前剩余票数:" + ticketSum);
}
} catch (InterruptedException ignored) {
}
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(4);
// 模拟4个线程卖票
executorService.execute(() -> SellDemo.sell());
executorService.execute(() -> SellDemo.sell());
executorService.execute(() -> SellDemo.sell());
executorService.execute(() -> SellDemo.sell());
}
}
介绍
悲观锁比较悲观,它认为如果不锁住这个资源,别的线程就会来争抢,就会造成数据结果错误,所以悲观锁为了确保结果的正确性,会在每次获取并修改数据时,都把数据锁住,让其他线程无法访问该数据,这样就可以确保数据内容万无一失。
JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLock,都是悲观锁
synchronized
加锁
public class SynchronizedDemo {
/**
* 库存
*/
static int ticketSum = 100;
/**
* 卖票
*/
public synchronized static void sell() {
for (int i = 0; i < 100; i++) {
try {
if (ticketSum > 0) {
// 模拟业务处理时间
Thread.sleep(10);
ticketSum--;
System.out.println(Thread.currentThread().getName() + "卖票成功,当前剩余票数:" + ticketSum);
}
} catch (InterruptedException ignored) {
}
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(4);
// 模拟4个线程卖票
executorService.execute(() -> SynchronizedDemo.sell());
executorService.execute(() -> SynchronizedDemo.sell());
executorService.execute(() -> SynchronizedDemo.sell());
executorService.execute(() -> SynchronizedDemo.sell());
}
}
ReentrantLock
加锁public class ReentrantLockDemo {
/**
* 锁
*/
private static final Lock LOCK = new ReentrantLock();
/**
* 库存
*/
static int ticketSum = 100;
public static void sell() {
for (int i = 0; i < 100; i++) {
LOCK.lock();
try {
if (ticketSum > 0) {
// 模拟业务处理时间
Thread.sleep(10);
ticketSum--;
System.out.println(Thread.currentThread().getName() + "卖票成功,当前剩余票数:" + ticketSum);
}
} catch (InterruptedException ignored) {
} finally {
LOCK.unlock();
}
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(4);
// 模拟4个线程卖票
executorService.execute(() -> ReentrantLockDemo.sell());
executorService.execute(() -> ReentrantLockDemo.sell());
executorService.execute(() -> ReentrantLockDemo.sell());
executorService.execute(() -> ReentrantLockDemo.sell());
}
}
介绍
通过版本号一致与否,即给数据加上版本,不会上锁,判断版本号,可以多人操作。乐观锁适用于多读的应用类型,这样可以提高吞吐量。
乐观锁的典型案例就是原子类,例如 AtomicInteger 在更新数据时,就使用了乐观锁的思想,多个线程可以同时操作同一个原子变量。
乐观锁实现
public class OptimisticDemo {
/**
* 用原子类定义变量
* 库存
*/
static AtomicInteger ticketSum = new AtomicInteger(100);
public static void sell() {
for (int i = 0; i < 100; i++) {
try {
// 获取进行-1后的票数,修改和获取必须同时进行。
int now = ticketSum.addAndGet(-1);
if (now < 0) {
break;
}
// 模拟业务处理时间
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "卖票成功,当前剩余票数:" + now);
} catch (InterruptedException ignored) {
}
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(4);
// 模拟4个线程卖票
executorService.execute(() -> OptimisticDemo.sell());
executorService.execute(() -> OptimisticDemo.sell());
executorService.execute(() -> OptimisticDemo.sell());
executorService.execute(() -> OptimisticDemo.sell());
}
}
ReentrantLock
一样,Lock lock = new ReentrantLock(true)
在构造中传入true即可开启公平锁,false 表示非公平锁。public class ReentrantLockDemo {
/**
* true 表示公平锁
*/
private static final Lock LOCK = new ReentrantLock(true);
/**
* 库存
*/
static int ticketSum = 100;
public static void sell() {
for (int i = 0; i < 100; i++) {
LOCK.lock();
try {
if (ticketSum > 0) {
// 模拟业务处理时间
Thread.sleep(10);
ticketSum--;
System.out.println(Thread.currentThread().getName() + "卖票成功,当前剩余票数:" + ticketSum);
}
} catch (InterruptedException ignored) {
} finally {
LOCK.unlock();
}
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(4);
// 模拟4个线程卖票
executorService.execute(() -> ReentrantLockDemo.sell());
executorService.execute(() -> ReentrantLockDemo.sell());
executorService.execute(() -> ReentrantLockDemo.sell());
executorService.execute(() -> ReentrantLockDemo.sell());
}
}
介绍
读写锁:一个资源可以被多个读线程访问,也可以被一个写线程访问,但不能同时存在读写线程,读写互斥,读读共享(写锁独占,读锁共享,写锁优先级高于读锁)
读写锁ReentrantReadWriteLock
读锁为ReentrantReadWriteLock.ReadLock
,readLock()
方法
写锁为ReentrantReadWriteLock.WriteLock
,writeLock()
方法
创建读写锁对象private ReadWriteLock rwLock = new ReentrantReadWriteLock()
写锁 加锁 rwLock.writeLock().lock()
,解锁为rwLock.writeLock().unlock()
读锁 加锁rwLock.readLock().lock()
,解锁为rwLock.readLock().unlock()
读写锁案例:修改卖票案例,新增读写方法,分开加锁。
public class ReentrantReadWriteDemo {
/**
* 创建读写锁
*/
private static final ReadWriteLock RW_LOCK = new ReentrantReadWriteLock();
/**
* 库存票数
*/
static int ticketSum = 100;
/**
* 获取库存
*/
public static int getSum() {
RW_LOCK.readLock().lock();
try {
return ticketSum;
} finally {
RW_LOCK.readLock().unlock();
}
}
/**
* 设置库存
*/
public static int setSum() {
RW_LOCK.writeLock().lock();
try {
if(ticketSum > 0){
ticketSum--;
}
return ticketSum;
} finally {
RW_LOCK.writeLock().unlock();
}
}
/**
* 卖票
*/
public static void sell() {
for (int i = 0; i < 100; i++) {
try {
int sum = setSum();
if (sum <= 0) {
System.out.println("卖完啦!");
break;
}
// 模拟业务处理时间
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "卖票成功,当前剩余票数:" + sum );
} catch (InterruptedException ignored) {
}
}
}
/**
* 打印剩余票数,模拟用户查询票数
*/
public static void print() {
while(true) {
try {
System.out.println("用户查询票数,当前剩余票数:" + getSum() );
Thread.sleep(1000);
} catch (InterruptedException ignored) {
}
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(4);
// 模拟4个线程卖票
executorService.execute(() -> ReentrantReadWriteDemo.sell());
executorService.execute(() -> ReentrantReadWriteDemo.sell());
executorService.execute(() -> ReentrantReadWriteDemo.sell());
executorService.execute(() -> ReentrantReadWriteDemo.sell());
// 模拟1个线程查询剩余票数
executorService.execute(() -> ReentrantReadWriteDemo.print());
}
}
synchronized
和ReentrantLock
都是可重入锁synchronized
案例public class Reentrant {
public static void test() {
Object o = new Object();
new Thread(() -> {
synchronized (o) {
System.out.println(Thread.currentThread().getName() + "买1");
synchronized (o) {
System.out.println(Thread.currentThread().getName() + "送1");
synchronized (o) {
System.out.println(Thread.currentThread().getName() + "再送");
}
}
}
}, "线程:").start();
}
public static void main(String[] args) {
test();
}
}
ReentrantLock
案例public class ReentrantLockTest {
private static final Lock LOCK = new ReentrantLock();
public static void test() {
LOCK.lock();
try {
System.out.println(Thread.currentThread().getName() + "买1");
LOCK.lock();
try {
System.out.println(Thread.currentThread().getName() + "送1");
LOCK.lock();
try {
System.out.println(Thread.currentThread().getName() + "再送");
} finally {
LOCK.unlock();
}
} finally {
LOCK.unlock();
}
} finally {
LOCK.unlock();
}
}
public static void main(String[] args) {
new Thread(() -> {
test();
}, "线程:").start();
}
}
介绍
自旋锁是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。
优点:
多个线程对同一个变量一直使用CAS操作,那么会有大量修改操作,从而产生大量的缓存一致性流量,因为每一次CAS操作都会发出广播通知其他处理器,从而影响程序的性能。
缺点:
自旋锁一直占用CPU,在未获得锁的情况下,一直运行,如果不能在很短的时间内获得锁,会导致CPU效率降低。
总结:
由此可见,我们要慎重的使用自旋锁,自旋锁适合于锁使用者保持锁时间比较短并且锁竞争不激烈的情况。正是由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。
利用CSA实现自旋锁
public class SpinLock {
private static final AtomicReference<Thread> CAS = new AtomicReference<>();
public static void lock() {
Thread current = Thread.currentThread();
// 利用CAS,compare比较CAS中值是否为空,为空则把值更新为新的线程并返回true,否则一直循环运行,线程保持运行不被挂起
while (!CAS.compareAndSet(null, current)) {
}
// DO nothing
System.out.println(Thread.currentThread().getName() + "获得锁");
}
public static void unlock() {
// 解锁也很简单,compare比较当前线程是否拥有锁,拥有则把CAS中的值重新设空即可
Thread current = Thread.currentThread();
CAS.compareAndSet(current, null);
}
}
public class SpinLockSellDemo {
private static final AtomicReference<Thread> CAS = new AtomicReference<>();
/**
* 库存
*/
static int ticketSum = 100;
public static void lock() {
Thread current = Thread.currentThread();
// 利用CAS,compare比较CAS中值是否为空,为空则把值更新为新的线程并返回true,否则一直循环运行
while (!CAS.compareAndSet(null, current)) {
}
}
public static void unlock() {
// 解锁也很简单,compare比较当前线程是否拥有锁,拥有则把CAS中的值重新设空即可
Thread current = Thread.currentThread();
CAS.compareAndSet(current, null);
}
/**
* 卖票
*/
public static void sell() {
for (int i = 0; i < 100; i++) {
try {
lock();
try {
if (ticketSum > 0) {
// 模拟业务处理时间
Thread.sleep(10);
ticketSum--;
System.out.println(Thread.currentThread().getName() + "卖票成功,当前剩余票数:" + ticketSum);
}
} catch (InterruptedException ignored) {
}
} finally {
unlock();
}
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(4);
// 模拟4个线程卖票
executorService.execute(() -> SpinLockSellDemo.sell());
executorService.execute(() -> SpinLockSellDemo.sell());
executorService.execute(() -> SpinLockSellDemo.sell());
executorService.execute(() -> SpinLockSellDemo.sell());
}
}