李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
Java
正文
31.共享模型之无锁问题提出
Leefs
2022-11-03 PM
849℃
0条
[TOC] ### 一、引述 > 需求:保证多线程情况下 **`account.withdraw`** 取款方法的线程安全 ```java import java.util.ArrayList; import java.util.List; /** * @author lilinchao * @date 2022-11-03 * @description 取款方法 **/ public interface Account { //获取余额 Integer getBalance(); //取款 void withdraw(Integer amount); /** * 方法内会启动 1000 个线程,每个线程做 -10 元 的操作 * 如果初始余额为 10000 那么正确的结果应当是 0 */ static void demo(Account account){ List
threadList = new ArrayList<>(); long start = System.nanoTime(); for (int i = 0; i < 1000; i++){ threadList.add(new Thread(() -> { account.withdraw(10); })); } threadList.forEach(Thread::start); threadList.forEach(t -> { try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } }); long end = System.nanoTime(); System.out.println(account.getBalance() + " cost: " + (end-start)/1000_000 + " ms"); } } ``` + **方法调用** `AccountUnsafe`类实现Account接口 ```java /** * @author lilinchao * @date 2022-11-03 * @description 方法调用 **/ public class Test01 { public static void main(String[] args) { Account.demo(new AccountUnsafe(10000)); } } class AccountUnsafe implements Account { private Integer blance; public AccountUnsafe(Integer blance){ this.blance = blance; } @Override public Integer getBalance() { return blance; } @Override public void withdraw(Integer amount) { blance -= amount; } } ``` **运行结果** ``` 250 cost: 86 ms ``` **说明** 总共有10000元,一千个线程,每个线程取走10元,结果应该是0。但是,运行结果是250,和预期结果不符,很明显,线程并不是安全的。 ### 二、为什么不安全 + **withdraw 方法** ```java public void withdraw(Integer amount) { balance -= amount; } ``` + **对应在字节码** ```java ALOAD 0 // <- this ALOAD 0 GETFIELD com/lilinchao/AccountUnsafe.balance : Ljava/lang/Integer; // <- this.balance INVOKEVIRTUAL java/lang/Integer.intValue ()I // 拆箱 ALOAD 1 // <- amount INVOKEVIRTUAL java/lang/Integer.intValue ()I // 拆箱 ISUB // 减法 INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer; // 结果装箱 PUTFIELD com/lilinchao/AccountUnsafe.balance : Ljava/lang/Integer; // -> this.balance ``` + **多线程执行流程** ```java //Thread-0 ALOAD 0 // thread-0 <- this ALOAD 0 GETFIELD com/lilinchao/AccountUnsafe.balance // thread-0 <- this.balance INVOKEVIRTUAL java/lang/Integer.intValue // thread-0 拆箱 ALOAD 1 // thread-0 <- amount INVOKEVIRTUAL java/lang/Integer.intValue // thread-0 拆箱 ISUB // thread-0 减法 INVOKESTATIC java/lang/Integer.valueOf // thread-0 结果装箱 PUTFIELD com/lilinchao/AccountUnsafe.balance // thread-0 -> this.balance --------------------------------------------------------------------------------------- //thread-1 ALOAD 0 // thread-1 <- this ALOAD 0 GETFIELD com/lilinchao/AccountUnsafe.balance // thread-1 <- this.balance INVOKEVIRTUAL java/lang/Integer.intValue // thread-1 拆箱 ALOAD 1 // thread-1 <- amount INVOKEVIRTUAL java/lang/Integer.intValue // thread-1 拆箱 ISUB // thread-1 减法 INVOKESTATIC java/lang/Integer.valueOf // thread-1 结果装箱 PUTFIELD com/lilinchao/AccountUnsafe.balance // thread-1 -> this.balance ``` ### 三、解决思路-锁 首先想到的是给 Account 对象加锁 + 在`getBalance`和`withdraw`方法上加synchronized锁 ```java /** * @author lilinchao * @date 2022-11-03 * @description 解决思路-锁 **/ public class Test03 { public static void main(String[] args) { Account.demo(new AccountUnsafe2(10000)); } } class AccountUnsafe2 implements Account { private Integer blance; public AccountUnsafe2(Integer blance){ this.blance = blance; } @Override public synchronized Integer getBalance() { return blance; } @Override public synchronized void withdraw(Integer amount) { blance -= amount; } } ``` **运行结果** ``` 0 cost: 77 ms ``` ### 四、解决思路-无锁 ```java import java.util.concurrent.atomic.AtomicInteger; /** * @author lilinchao * @date 2022-11-03 * @description 解决思路-无锁 **/ public class Test02 { public static void main(String[] args) { Account.demo(new AccountSafe(10000)); } } class AccountSafe implements Account { private AtomicInteger balance; public AccountSafe(Integer balance){ this.balance = new AtomicInteger(balance); } @Override public Integer getBalance() { return balance.get(); } @Override public void withdraw(Integer amount) { while (true) { int prev = balance.get(); int next = prev - amount; if (balance.compareAndSet(prev,next)){ break; } } } } ``` **运行结果** ``` 0 cost: 72 ms ``` *附参考原文链接* *《黑马程序员之并发编程》*
标签:
并发编程
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:
https://lilinchao.com/archives/2548.html
上一篇
30.并发编程之单例模式安全习题
下一篇
32.共享模型之CAS与volatile
评论已关闭
栏目分类
随笔
2
Java
326
大数据
229
工具
31
其它
25
GO
47
NLP
4
标签云
锁
Quartz
JavaSE
Spark Streaming
FileBeat
HDFS
队列
Hive
GET和POST
Eclipse
NIO
持有对象
并发线程
ClickHouse
Http
Java工具类
Kafka
栈
Filter
JVM
设计模式
Flink
DataX
链表
机器学习
SpringCloud
Docker
JavaWEB项目搭建
数据结构
Elasticsearch
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞
评论已关闭