Java关于锁/并发的一点疑惑(含示例)
事情是这样的,我在做一点锁的小练习,模拟多线程去做库存扣减。容我先贴上代码
static class Stock {
private int value = 10000000;
private int version;
private final ReentrantReadWriteLock stockLock = new ReentrantReadWriteLock();
public int getVersion() {
return version;
}
public int getStockValue() {
stockLock.readLock().lock();
try {
return value;
} finally {
stockLock.readLock().unlock();
}
}
public void reduceStock(int reduceCount, int version) {
stockLock.writeLock().lock();
try {
if (this.version == version) {
value = value - reduceCount;
this.version++;
}
} finally {
stockLock.writeLock().unlock();
}
}
}
static class StockMonitor implements Runnable {
final Stock stock;
final long frequency;
StockMonitor(Stock stock, long frequency) {
this.stock = stock;
this.frequency = frequency;
}
@Override
public void run() {
for (int value = stock.getStockValue(); value > 0; value = stock.getStockValue()) {
System.out.format("%s: stock:%s version:%s%n", Thread.currentThread().getName(), value, stock.getVersion());
try {
Thread.sleep(frequency);
} catch (InterruptedException e) {
break;
}
}
System.out.format("%s: 最终stock: %s%n", Thread.currentThread().getName(), stock.getStockValue());
}
}
static class StockReducer implements Runnable {
final Stock stock;
final int reduceAmount;
StockReducer(Stock stock, int reduceAmount) {
this.stock = stock;
this.reduceAmount = reduceAmount;
}
@Override
public void run() {
while (stock.getStockValue() >= reduceAmount && !Thread.currentThread().isInterrupted()) {
int version = stock.getVersion();
stock.reduceStock(reduceAmount, version);
}
if (Thread.interrupted()) {
System.out.format("%s: 我被中断了!%n", Thread.currentThread().getName());
}
}
}
private static void runTask(int count) {
final Stock stock = new Stock();
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
2,
200,
10,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2)
);
StockMonitor monitor1 = new StockMonitor(stock, 100);
Thread m1 = new Thread(monitor1);
m1.start();
for (int i = 0; i < 3; i++) {
poolExecutor.execute(new StockReducer(stock, (int)(Math.random() * 10 + 1)));
}
poolExecutor.execute(new StockReducer(stock, 1));
long start = System.currentTimeMillis();
while (m1.isAlive()) {}
long end = System.currentTimeMillis();
poolExecutor.shutdownNow();
while (!poolExecutor.isTerminated()) {}
System.out.format("第%s次耗时: %ss%n", count, ((end - start) / 1000.0));
}
public static void main(String[] args) {
for (int i = 1; i < 101; i++) {
runTask(i);
}
}
代码中定义了一个共享资源Stock,一个StockReducer,一个StockMonitor。StockMonitor作用就是打印Stock状态,StockReducer作用是扣减库存。
人菜瘾大,我试图模拟了一下CAS,想测一下随着线程地增加性能会有多少递减来着。
我在准备运行代码前还感觉良好,然而现实立刻给予了我重击。现在咱们来看看输出结果:
Thread-0: stock:10000000 version:0
Thread-0: stock:4644660 version:891567
Thread-0: 最终stock: -8
第1次耗时: 0.241s
Thread-1: stock:10000000 version:0
Thread-1: stock:4965258 version:718427
Thread-1: 最终stock: 0
第2次耗时: 0.221s
Thread-2: stock:10000000 version:0
Thread-2: stock:4173631 version:896154
Thread-2: 最终stock: 0
第3次耗时: 0.223s
Thread-3: stock:10000000 version:0
Thread-3: stock:6127375 version:979329
Thread-3: stock:2475406 version:1884079
Thread-3: 最终stock: 0
第4次耗时: 0.336s
Thread-4: stock:10000000 version:0
Thread-4: stock:3816026 version:880387
Thread-4: 最终stock: 0
第5次耗时: 0.223s
Thread-5: stock:10000000 version:0
Thread-5: stock:2955524 version:883127
Thread-5: 最终stock: 0
......
跑了好几次,抓到了一个第一次就库存超卖的结果。
我疑惑的是,在reduceStock方法内我加了写锁,怎么还是会有线程跑进if条件呢。我尝试过给version加volitial,试过在if条件加上value > 0,都还是会出现这个问题。
还有一个有意思的现象,当我把线程数+1,也就是在runTask方法里将for的i < 4。这时运行一次循环的时间平均是i < 3的十倍左右,以下是线程+1后的输出结果:
Thread-0: stock:10000000 version:0
Thread-0: stock:9720622 version:106648
Thread-0: stock:9308132 version:267112
Thread-0: stock:8879434 version:431788
Thread-0: stock:8446031 version:593737
Thread-0: stock:8017534 version:756205
Thread-0: stock:7589054 version:921154
Thread-0: stock:7149642 version:1087798
Thread-0: stock:6698148 version:1254978
Thread-0: stock:6252264 version:1424765
Thread-0: stock:5807580 version:1593240
Thread-0: stock:5357400 version:1760675
Thread-0: stock:4894778 version:1927998
Thread-0: stock:4446009 version:2094625
Thread-0: stock:3995518 version:2263108
Thread-0: stock:3531879 version:2430541
Thread-0: stock:3082959 version:2597376
Thread-0: stock:2633458 version:2762137
Thread-0: stock:2188267 version:2927416
Thread-0: stock:1733464 version:3095106
Thread-0: stock:1269690 version:3265381
Thread-0: stock:807281 version:3435110
Thread-0: stock:322541 version:3607455
Thread-0: 最终stock: 0
第1次耗时: 2.583s
Thread-1: stock:10000000 version:0
Thread-1: stock:9344872 version:156057
Thread-1: stock:8685019 version:333065
Thread-1: stock:7988600 version:509706
Thread-1: stock:7367318 version:685235
Thread-1: stock:6738699 version:861870
Thread-1: stock:6117508 version:1037418
Thread-1: stock:5438947 version:1214298
Thread-1: stock:4860224 version:1391463
Thread-1: stock:4206301 version:1568004
Thread-1: stock:3610213 version:1744932
Thread-1: stock:2996725 version:1921401
Thread-1: stock:2355161 version:2097362
Thread-1: stock:1705939 version:2274564
Thread-1: stock:1096618 version:2448457
Thread-1: stock:523388 version:2623930
Thread-1: stock:20806 version:2800975
Thread-1: 最终stock: 0
第2次耗时: 1.901s
对于这个现象我也是想不明白,而且随着线程数增加,出现库存超卖的频率变少了(据我多次观察,绝不是时间长的我懒得看下去.jpg)
在此请教各位大佬指教!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
还是会出现负数,那么问题应该还是在reduceStock方法中,虽然前面while循环中有
stock.getStockValue() >= reduceAmount
这个判断,但是不保证在执行时不会发生变化,所以还需要在reduceStock方法里面获取锁之后再进行一次判断。可以试试下面的这个
还可以在获取锁之前加判断
我这边跑完了100次,没有出现负数
我也是人菜瘾大,既然你想测试cas,我觉得你的写法很矛盾,又是读写锁又是cas,很难抓住重点,所以我改造了下,更加清晰,明了。