java线程安全例子_Java总结篇系列:Java多线程(三)

本文主要接着前面多线程的两篇文章总结Java多线程中的线程安全问题。

一.一个典型的Java线程安全例子

1 public classThreadTest {2

3 public static voidmain(String[] args) {4 Account account = new Account("123456", 1000);5 DrawMoneyRunnable drawMoneyRunnable = new DrawMoneyRunnable(account, 700);6 Thread myThread1 = newThread(drawMoneyRunnable);7 Thread myThread2 = newThread(drawMoneyRunnable);8 myThread1.start();9 myThread2.start();10 }11

12 }13

14 class DrawMoneyRunnable implementsRunnable {15

16 privateAccount account;17 private doubledrawAmount;18

19 public DrawMoneyRunnable(Account account, doubledrawAmount) {20 super();21 this.account =account;22 this.drawAmount =drawAmount;23 }24

25 public voidrun() {26 if (account.getBalance() >= drawAmount) { //1

27 System.out.println("取钱成功, 取出钱数为:" +drawAmount);28 double balance = account.getBalance() -drawAmount;29 account.setBalance(balance);30 System.out.println("余额为:" +balance);31 }32 }33 }34

35 classAccount {36

37 privateString accountNo;38 private doublebalance;39

40 publicAccount() {41

42 }43

44 public Account(String accountNo, doublebalance) {45 this.accountNo =accountNo;46 this.balance =balance;47 }48

49 publicString getAccountNo() {50 returnaccountNo;51 }52

53 public voidsetAccountNo(String accountNo) {54 this.accountNo =accountNo;55 }56

57 public doublegetBalance() {58 returnbalance;59 }60

61 public void setBalance(doublebalance) {62 this.balance =balance;63 }64

65 }

上面例子很容易理解,有一张银行卡,里面有1000的余额,程序模拟你和你老婆同时在取款机进行取钱操作的场景。多次运行此程序,可能具有多个不同组合的输出结果。其中一种可能的输出为:

1 取钱成功, 取出钱数为:700.0

2 余额为:300.0

3 取钱成功, 取出钱数为:700.0

4 余额为:-400.0

也就是说,对于一张只有1000余额的银行卡,你们一共可以取出1400,这显然是有问题的。

经过分析,问题在于Java多线程环境下的执行的不确定性。CPU可能随机的在多个处于就绪状态中的线程中进行切换,因此,很有可能出现如下情况:当thread1执行到//1处代码时,判断条件为true,此时CPU切换到thread2,执行//1处代码,发现依然为真,然后执行完thread2,接着切换到thread1,接着执行完毕。此时,就会出现上述结果。

因此,讲到线程安全问题,其实是指多线程环境下对共享资源的访问可能会引起此共享资源的不一致性。因此,为避免线程安全问题,应该避免多线程环境下对此共享资源的并发访问。

二.同步方法

对共享资源进行访问的方法定义中加上synchronized关键字修饰,使得此方法称为同步方法。可以简单理解成对此方法进行了加锁,其锁对象为当前方法所在的对象自身。多线程环境下,当执行此方法时,首先都要获得此同步锁(且同时最多只有一个线程能够获得),只有当线程执行完此同步方法后,才会释放锁对象,其他的线程才有可能获取此同步锁,以此类推...

在上例中,共享资源为account对象,当使用同步方法时,可以解决线程安全问题。只需在run()方法前加上synshronized关键字即可。

1 public synchronized voidrun() {2

3 //....

4

5 }

三.同步代码块

正如上面所分析的那样,解决线程安全问题其实只需限制对共享资源访问的不确定性即可。使用同步方法时,使得整个方法体都成为了同步执行状态,会使得可能出现同步范围过大的情况,于是,针对需要同步的代码可以直接另一种同步方式——同步代码块来解决。

同步代码块的格式为:

1 synchronized(obj) {2

3 //...

4

5 }

其中,obj为锁对象,因此,选择哪一个对象作为锁是至关重要的。一般情况下,都是选择此共享资源对象作为锁对象。

如上例中,最好选用account对象作为锁对象。(当然,选用this也是可以的,那是因为创建线程使用了runnable方式,如果是直接继承Thread方式创建的线程,使用this对象作为同步锁会其实没有起到任何作用,因为是不同的对象了。因此,选择同步锁时需要格外小心...)

四.Lock对象同步锁

上面我们可以看出,正因为对同步锁对象的选择需要如此小心,有没有什么简单点的解决方案呢?以方便同步锁对象与共享资源解耦,同时又能很好的解决线程安全问题。

使用Lock对象同步锁可以方便的解决此问题,唯一需要注意的一点是Lock对象需要与资源对象同样具有一对一的关系。Lock对象同步锁一般格式为:

1 classX {2

3 //显示定义Lock同步锁对象,此对象与共享资源具有一对一关系

4 private final Lock lock = newReentrantLock();5

6 public voidm(){7 //加锁

8 lock.lock();9

10 //... 需要进行线程安全同步的代码11

12 //释放Lock锁

13 lock.unlock();14 }15

16 }

五.wait()/notify()/notifyAll()线程通信

在博文《Java总结篇系列:java.lang.Object》中有提及到这三个方法,虽然这三个方法主要都是用于多线程中,但实际上都是Object类中的本地方法。因此,理论上,任何Object对象都可以作为这三个方法的主调,在实际的多线程编程中,只有同步锁对象调这三个方法,才能完成对多线程间的线程通信。

wait():导致当前线程等待并使其进入到等待阻塞状态。直到其他线程调用该同步锁对象的notify()或notifyAll()方法来唤醒此线程。

notify():唤醒在此同步锁对象上等待的单个线程,如果有多个线程都在此同步锁对象上等待,则会任意选择其中某个线程进行唤醒操作,只有当前线程放弃对同步锁对象的锁定,才可能执行被唤醒的线程。

notifyAll():唤醒在此同步锁对象上等待的所有线程,只有当前线程放弃对同步锁对象的锁定,才可能执行被唤醒的线程。

1 packagecom.qqyumidi;2

3 public classThreadTest {4

5 public static voidmain(String[] args) {6 Account account = new Account("123456", 0);7

8 Thread drawMoneyThread = new DrawMoneyThread("取钱线程", account, 700);9 Thread depositeMoneyThread = new DepositeMoneyThread("存钱线程", account, 700);10

11 drawMoneyThread.start();12 depositeMoneyThread.start();13 }14

15 }16

17 class DrawMoneyThread extendsThread {18

19 privateAccount account;20 private doubleamount;21

22 public DrawMoneyThread(String threadName, Account account, doubleamount) {23 super(threadName);24 this.account =account;25 this.amount =amount;26 }27

28 public voidrun() {29 for (int i = 0; i < 100; i++) {30 account.draw(amount, i);31 }32 }33 }34

35 class DepositeMoneyThread extendsThread {36

37 privateAccount account;38 private doubleamount;39

40 public DepositeMoneyThread(String threadName, Account account, doubleamount) {41 super(threadName);42 this.account =account;43 this.amount =amount;44 }45

46 public voidrun() {47 for (int i = 0; i < 100; i++) {48 account.deposite(amount, i);49 }50 }51 }52

53 classAccount {54

55 privateString accountNo;56 private doublebalance;57 //标识账户中是否已有存款

58 private boolean flag = false;59

60 publicAccount() {61

62 }63

64 public Account(String accountNo, doublebalance) {65 this.accountNo =accountNo;66 this.balance =balance;67 }68

69 publicString getAccountNo() {70 returnaccountNo;71 }72

73 public voidsetAccountNo(String accountNo) {74 this.accountNo =accountNo;75 }76

77 public doublegetBalance() {78 returnbalance;79 }80

81 public void setBalance(doublebalance) {82 this.balance =balance;83 }84

85 /**

86 * 存钱87 *88 *@paramdepositeAmount89 */

90 public synchronized void deposite(double depositeAmount, inti) {91

92 if(flag) {93 //账户中已有人存钱进去,此时当前线程需要等待阻塞

94 try{95 System.out.println(Thread.currentThread().getName() + " 开始要执行wait操作" + " -- i=" +i);96 wait();97 //1

98 System.out.println(Thread.currentThread().getName() + " 执行了wait操作" + " -- i=" +i);99 } catch(InterruptedException e) {100 e.printStackTrace();101 }102 } else{103 //开始存钱

104 System.out.println(Thread.currentThread().getName() + " 存款:" + depositeAmount + " -- i=" +i);105 setBalance(balance +depositeAmount);106 flag = true;107

108 //唤醒其他线程

109 notifyAll();110

111 //2

112 try{113 Thread.sleep(3000);114 } catch(InterruptedException e) {115 e.printStackTrace();116 }117 System.out.println(Thread.currentThread().getName() + "-- 存钱 -- 执行完毕" + " -- i=" +i);118 }119 }120

121 /**

122 * 取钱123 *124 *@paramdrawAmount125 */

126 public synchronized void draw(double drawAmount, inti) {127 if (!flag) {128 //账户中还没人存钱进去,此时当前线程需要等待阻塞

129 try{130 System.out.println(Thread.currentThread().getName() + " 开始要执行wait操作" + " 执行了wait操作" + " -- i=" +i);131 wait();132 System.out.println(Thread.currentThread().getName() + " 执行了wait操作" + " 执行了wait操作" + " -- i=" +i);133 } catch(InterruptedException e) {134 e.printStackTrace();135 }136 } else{137 //开始取钱

138 System.out.println(Thread.currentThread().getName() + " 取钱:" + drawAmount + " -- i=" +i);139 setBalance(getBalance() -drawAmount);140

141 flag = false;142

143 //唤醒其他线程

144 notifyAll();145

146 System.out.println(Thread.currentThread().getName() + "-- 取钱 -- 执行完毕" + " -- i=" + i); //3

147 }148 }149

150 }

上面的例子演示了wait()/notify()/notifyAll()的用法。部分输出结果为:

1 取钱线程 开始要执行wait操作 执行了wait操作 -- i=0

2 存钱线程 存款:700.0 -- i=0

3 存钱线程-- 存钱 -- 执行完毕 -- i=0

4 存钱线程 开始要执行wait操作 -- i=1

5 取钱线程 执行了wait操作 执行了wait操作 -- i=0

6 取钱线程 取钱:700.0 -- i=1

7 取钱线程-- 取钱 -- 执行完毕 -- i=1

8 取钱线程 开始要执行wait操作 执行了wait操作 -- i=2

9 存钱线程 执行了wait操作 -- i=1

10 存钱线程 存款:700.0 -- i=2

11 存钱线程-- 存钱 -- 执行完毕 -- i=2

12 取钱线程 执行了wait操作 执行了wait操作 -- i=2

13 取钱线程 取钱:700.0 -- i=3

14 取钱线程-- 取钱 -- 执行完毕 -- i=3

15 取钱线程 开始要执行wait操作 执行了wait操作 -- i=4

16 存钱线程 存款:700.0 -- i=3

17 存钱线程-- 存钱 -- 执行完毕 -- i=3

18 存钱线程 开始要执行wait操作 -- i=4

19 取钱线程 执行了wait操作 执行了wait操作 -- i=4

20 取钱线程 取钱:700.0 -- i=5

21 取钱线程-- 取钱 -- 执行完毕 -- i=5

22 取钱线程 开始要执行wait操作 执行了wait操作 -- i=6

23 存钱线程 执行了wait操作 -- i=4

24 存钱线程 存款:700.0 -- i=5

25 存钱线程-- 存钱 -- 执行完毕 -- i=5

26 存钱线程 开始要执行wait操作 -- i=6

27 取钱线程 执行了wait操作 执行了wait操作 -- i=6

28 取钱线程 取钱:700.0 -- i=7

29 取钱线程-- 取钱 -- 执行完毕 -- i=7

30 取钱线程 开始要执行wait操作 执行了wait操作 -- i=8

31 存钱线程 执行了wait操作 -- i=6

32 存钱线程 存款:700.0 -- i=7

由此,我们需要注意如下几点:

1.wait()方法执行后,当前线程立即进入到等待阻塞状态,其后面的代码不会执行;

2.notify()/notifyAll()方法执行后,将唤醒此同步锁对象上的(任意一个-notify()/所有-notifyAll())线程对象,但是,此时还并没有释放同步锁对象,也就是说,如果notify()/notifyAll()后面还有代码,还会继续进行,知道当前线程执行完毕才会释放同步锁对象;

3.notify()/notifyAll()执行后,如果右面有sleep()方法,则会使当前线程进入到阻塞状态,但是同步对象锁没有释放,依然自己保留,那么一定时候后还是会继续执行此线程,接下来同2;

4.wait()/notify()/nitifyAll()完成线程间的通信或协作都是基于不同对象锁的,因此,如果是不同的同步对象锁将失去意义,同时,同步对象锁最好是与共享资源对象保持一一对应关系;

5.当wait线程唤醒后并执行时,是接着上次执行到的wait()方法代码后面继续往下执行的。

当然,上面的例子相对来说比较简单,只是为了简单示例wait()/notify()/noitifyAll()方法的用法,但其本质上说,已经是一个简单的生产者-消费者模式了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/555264.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Grammarly:最优秀的日常英文写作辅助工具——论文英文校验

使用Grammarly也有一些心得&#xff0c;在此分享给大家。 1&#xff0c;Grammarly是什么&#xff1f; Grammarly是一款在线语法纠正和校对工具&#xff0c;支持Windows、Mac、iOS和Android等多个平台。它能够检查单词拼写、纠正标点符号、修正语法错误、调整语气以及给出风格…

Spring如何将@RestController的对象自动转换为json_@ResponseBody 注解原理

我正在看一段代码&#xff0c;其中我假设spring决定在幕后使用Jackson为RestController自动将对象转换为json RestController RequestMapping("/api") public class ApiController {private RoomServices roomServices;Autowiredpublic ApiController(RoomServices…

jetty java web_i-jetty 下的JavaWeb开发(一)

最近需要将android端作为服务器进行开发&#xff0c;让android作为服务器&#xff0c;现阶段技术并不是很成熟&#xff0c;主要的服务器有i-jetty&#xff0c;是基于PC端的jetty的移植。i-jetty同tomcat类似&#xff0c;也是Servlet的容器&#xff0c;但是i-jetty需要使用andro…

SpringMVC @RequestBody和@ResponseBody原理解析

SpringMVC RequestBody和ResponseBody原理解析 前言 RequestBody作用是将http请求解析为对应的对象。例如&#xff1a; http请求的参数&#xff08;application/json格式&#xff09;&#xff1a; {"accountId": 10,"adGroupId": "12345678",…

java 高性能缓存_高性能Java缓存----Caffeine

简单介绍Caffeine是新出现的一个高性能的Java缓存&#xff0c;有了它完全可以代替Guava Cache&#xff0c;来实现更加高效的缓存&#xff1b;Caffeine采用了W-TinyLFU回收策略&#xff0c;集合了LRU和LFU的优点&#xff0c;提供了一个最佳的命中率&#xff0c;在效率上可以秒杀…

@ResponseBody 转化成json后与实体类字段名不一致_SpringMVC字符串解析成json对象(@RequestBody注解和@ResponseBody注解)

ResponseBody 转化成json后与实体类字段名不一致 实体类A字段名由B改成C后&#xff0c;Controller 中返回的List中字段名仍然是C 经过ResponseBody返回到前台后又变成了B 后来发现公司项目采用的是阿里的fastjson&#xff0c; 是开源的Json格式化工具库 此工具库是根据实体类…

java togglebutton_ToggleButton和Switch使用大全

本文转载自&#xff1a;Android零基础入门第21节&#xff1a;ToggleButton和Switch使用大全http://www.apkbus.com/blog-205190-68463.html(出处: 安卓巴士 - 安卓开发 - Android开发 - 安卓 - 移动互联网门户)&#xff0c;转载应备注出处&#xff0c;尊重原创上期学习了CheckB…

Jackson,实现Bean和JSON之间的灵活转换(SpringMVC默认的JSON转换器)

Jackson介绍 Jackson是Java最受欢迎的JSON类库之一&#xff0c;包含两个不同的解析器&#xff1a; Jackson ObjectMapper&#xff0c;将JSON转化为Java对象&#xff0c;或者转换为Jackson特定的树结构Jackson JsonParser&#xff0c;JSON流解析器&#xff0c;每次只解析一个J…

MyBatis之工作原理,简单实体的增加、修改、删除、查询_Mybatis-原理总结

一、MyBatis之工作原理 MyBatis是一个半自动映射框架。所谓半自动&#xff0c;是相对Hibernate全表映射而言的&#xff0c;MyBatis需要手动匹配提供POJO、SQL和映射关系。 我们知道&#xff0c;JDBC有四个核心对象&#xff1a; &#xff08;1&#xff09;DriverManager&#…

MySQL优化:如何避免回表查询?_什么是索引覆盖?

数据库表结构&#xff1a; create table user (id int primary key,name varchar(20),sex varchar(5),index(name) )engineinnodb;select id,name where nameshenjianselect id,name,sex where nameshenjian多查询了一个属性&#xff0c;为何检索过程完全不同&#xff1f; 什…

mysql提示Column count doesn‘t match value count at row 1错误

我们在对数据库进行添加信息时可能会遇到如下错误&#xff1a; Column count doesn’t match value count at row 1 该错误的意思是传入表的字段数和values值的个数不一样 我总结了一下&#xff0c;主要有3个易错点&#xff1a; 1.要传入表中的字段数和values后面的值的个数不…

java 阅发布模式_redis发布订阅模式

一 前言虽然有消息队列&#xff0c;我们还是要了解一下redis发布订阅模式哟&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;二发布订阅模式PUBLISH 命令向通道发送信息&#xff0c;此客户端称为publisher 发布者&#xff1b;SUBSCRIBE 向命令通道订阅信息&#…

把实体 转为json 数据格式---jackson 的详细用法_Jackson快速入门

首先介绍三个注解&#xff1a; JsonAutoDetect (method/field):作用于方法或字段&#xff0c;用来表明&#xff0c;当生成json的时候忽略有该annotation的方法或字段 JsonIgnore 过滤不需要转成json的属性 JsonIgnoreProperties 主要用于过滤掉一些不需要的属性 以上三个注…

python 猴子补丁_python面试题精讲——monkey patch(猴子补丁)

前言本次依然是选自python面试题系列&#xff0c;将一个比较偏的概念&#xff0c;可能很多人没怎么听说过——猴子补丁&#xff0c;其实所讲的内容很简单&#xff0c;它得益于python灵活的语法、一切皆对象的思想&#xff0c;一起来看看看看吧&#xff01;目录一、什么是monkey…

java 类的加载顺序_Java 中类的加载顺序

这其实是去年校招时我遇到的一道阿里巴巴的笔试题(承认有点久远了-。-)&#xff0c;嗯&#xff0c;如果我没记错的话&#xff0c;当时是作为Java方向的一道选做大题。当然题意没有这么直白&#xff0c;题目只要求你写出程序运行后所有System.out.println的输出结果&#xff0c;…

Jackson转换json大写_关于jackson转化json的原理_jackson序列化和反序列化Json

背景 web工程中&#xff0c;数据交互是不可避免的&#xff0c;相比xml&#xff0c;json是现在流行的数据交互。 在调试接口中&#xff0c;发现返回字段的大小写不是我所期望的&#xff0c;原本应该返回的nNum字段变成了nnum&#xff0c;这样就导致和前端约定的有出入了。 ja…

groovy+mysql数据库_使用Groovy连接到MySQL

我正在尝试使用MAC OS 10.10.5 Yosemite上的以下Groovy代码连接到MySQL数据库import groovy.sql.Sqltry{def dbURL jdbc:mysql://localhost:3306/sakiladef dbUserName rootdef dbPassword Orange27def dbDriver com.mysql.jdbc.Driverlog.info(Good)def db Sql.newInstan…

svn利用TortoiseSVN忽略文件或文件夹(目录)

忽略已经版本控制的文件 如果你不小心添加了一些应该被忽略的文件&#xff0c;你如何将它们从版本控制中去除而不会丢失它们&#xff1f;或许你有自己的IDE配置文件&#xff0c;不是项目的一部分&#xff0c;但将会花费很多时间使之按照自己的方式工作。 如果你还没有提交&am…

java打印两个小人_[原创]Java画小人与阶梯问题的解答

package test;/**#Python源代码:#By:Cat73 QQ 1901803382#2014年7月22日19:33:12#画图函数 width:台阶的宽度(至少为4) hight:台阶的高度(至少为4) count:台阶的数量(至少为3)def paint(width, hight, count):for i in range(1, count):other(width, hight, count, i)#画出最后…

SpringMVC 参数绑定详解

概述 记得之前跟前端同事联调接口的时候&#xff0c;后端SpringMVC需要接收数组类型的参数&#xff0c;然后跟前端说需要传数组类型过来。后来前端童鞋传了数组&#xff0c;但是后端接收不成功&#xff0c;联调失败。那时候由于时间关系没有仔细研究这块&#xff0c;当时想了个…