什么是锁,为什么需要锁
在Java中,锁是一种同步机制,用于控制多个线程对共享资源的访问。共享资源可以是变量、方法或对象。当多个线程同时尝试访问共享资源时,可能会发生竞争条件,导致不可预测的行为。
数据错误例子
假设有一个简单的股票交易系统,其中两个线程分别代表买家和卖家。买家线程和卖家线程同时尝试购买和卖出股票,但是没有使用锁来保护共享资源。
- 买家线程和卖家线程同时检查库存,结果都发现还有股票可供购买或出售。于是,两个线程同时减少库存,导致库存数量减少两次,从而导致数据错误。
- 买家线程和卖家线程同时尝试增加库存,但由于买方和卖方都在尝试增加同一个变量(stockCount)的值,可能导致该变量的值不正确。
- 买家线程和卖家线程同时增加买家和卖家的股票数量,可能导致两个线程增加的数量不正确,从而导致数据错误。
为了确保对共享资源的访问不会发生冲突,需要使用锁来保证同一时刻只有一个线程能够访问共享资源。
悲观锁和乐观锁都是一种锁优化策略,用于减少锁的使用和锁竞争,从而提高并发性能。
什么是悲观锁
当多个线程同时尝试获取同一个锁时,悲观锁策略会假设最坏的情况,即假定多个线程同时获取锁会导致数据不一致。因此,它会采取一种较为保守的策略,避免多个线程同时获取锁。
对数据采取“悲观”态度,认为数据随时都有可能被别人改。
读取的时候就开始霸占,别人想改得排队,等我改完再说。
优点:
1. 有序队列
2. 无重试开销
3. 业务开发无需复杂的适配逻辑,符合线性开发思维
缺点:
- 产生饥饿:在某些情况下,悲观锁可能会导致饥饿现象,即某些线程长时间得不到锁,而其他线程却可以随时获得锁。这可能会导致某些线程长时间处于阻塞状态,从而影响程序的性能。
- 资源占用:悲观锁在等待锁的过程中会占用CPU资源,从而可能导致CPU资源占用过高。
- 死锁:在某些情况下,悲观锁可能会导致死锁现象,即多个线程相互等待对方释放锁,从而导致程序无法继续执行。
什么是乐观锁
乐观锁的核心思想是在获取锁之前先进行尝试性读取共享资源,如果读取成功,则认为没有其他线程同时访问该资源,可以获取锁并进行写入操作。如果读取失败,则说明有其他线程同时访问该资源,需要重新进行读取并尝试获取锁。
对数据采取“乐观”态度,认为我在修改数据的同时,数据大概率不会被别人动过。
读数据不独占,而是保存修改的时候再检查一下是否变更(同时),如果被改过了,就重读重做。
优点:
1. 无堵塞风险
2. 业务不排队,高效率优先
3. 最大化整体效率
缺点:
- 代码复杂性:在使用乐观锁时,需要考虑重试策略、超时等问题,这可能会导致代码复杂性增加,维护成本提高。
- 如果发生了频繁的业务间修改冲突,就会引发频繁重复的重试,造成计算资源的浪费。
- 并发读操作:在乐观锁中,读取操作不需要加锁,因此可能会有多个线程同时进行读取操作,这可能会导致读取结果不一致。
悲观锁与乐观锁哪个更好
各业务之间处理时间相近:悲观锁
各业务之间处理时间差异大:乐观锁
并非绝对,根据具体情况调整