Java多线程全学习笔记中.docx
- 文档编号:11108805
- 上传时间:2023-02-25
- 格式:DOCX
- 页数:32
- 大小:141.37KB
Java多线程全学习笔记中.docx
《Java多线程全学习笔记中.docx》由会员分享,可在线阅读,更多相关《Java多线程全学习笔记中.docx(32页珍藏版)》请在冰豆网上搜索。
Java多线程全学习笔记中
资源下载地址:
四.多线程的同步
以一个取钱列子来分析:
(用户登录那些省略)
Accout类:
[java]viewplaincopy
1/**银行取钱,账户类*/
2publicclassAccout{
3//账户编号
4privateStringaccoutNo;
5//账户余额
6privatedoublebalance;
7//账户名称
8privateStringaccoutName;
9publicAccout(){
10super();
11}
12publicAccout(StringaccoutNo,StringaccoutName,doublebalance){
13super();
14this.accoutNo=accoutNo;
15this.balance=balance;
16this.accoutName=accoutName;
17}
18publicStringgetAccoutNo(){
19returnaccoutNo;
20}
21publicvoidsetAccoutNo(StringaccoutNo){
22this.accoutNo=accoutNo;
23}
24publicdoublegetBalance(){
25returnbalance;
26}
27publicvoidsetBalance(doublebalance){
28this.balance=balance;
29}
30publicStringgetAccoutName(){
31returnaccoutName;
32}
33publicvoidsetAccoutName(StringaccoutName){
34this.accoutName=accoutName;
35}
36//根据accoutNohe来计算Accout的hashcode和判断equals
37@Override
38publicinthashCode(){
39returnaccoutNo.hashCode();
40}
41@Override
42publicbooleanequals(Objectobj){
43if(obj!
=null&&obj.getClass()==Accout.class){
44Accouttarget=(Accout)obj;
45returntarget.getAccoutNo().equals(accoutNo);
46}
47returnfalse;
48}
49}
DrawThread类:
[java]viewplaincopy
50/**取钱的线程类*/
51publicclassDrawThreadimplementsRunnable{
52//模拟用户账户
53privateAccoutaccout;
54//当前取钱线程所希望取得值
55privatedoubledrawAmount;
56publicDrawThread(Accoutaccout,doubledrawAmount){
57super();
58this.accout=accout;
59this.drawAmount=drawAmount;
60}
61//如果多个线程修改同一个共享数据时,会发生数据安全问题
62publicvoidrun(){
63//账户余额大于取款金额时
64if(accout.getBalance()>=drawAmount){
65//取款成功
66System.out.println(Thread.currentThread().getName()+accout.getAccoutName()+"取款成功:
吐出钞票:
"+drawAmount);
67//修改余额
68accout.setBalance(accout.getBalance()-drawAmount);
69System.out.println("当前余额为:
"+accout.getBalance());
70}
71//账户金额不够时
72else{
73System.out.println("账户金额不够,您的余额只有"+accout.getBalance());
74}
75}
76}
TestDraw测试类:
[java]viewplaincopy
77publicclassTestDraw{
78publicstaticvoidmain(String[]args)throwsInterruptedException{
79//创建一个用户
80Accoutacct=newAccout("123456","小明",1000);
81//模拟四个线程同时操作
82DrawThreaddt=newDrawThread(acct,600);
83//DrawThreaddt1=newDrawThread(acct,800);
84Threadth1=newThread(dt,"线程1");
85Threadth2=newThread(dt,"线程2");
86Threadth3=newThread(dt,"线程3");
87Threadth4=newThread(dt,"线程4");
88th4.join();
89th1.start();
90th2.start();
91th3.start();
92th4.start();
93}
94}
1.同步代码块
Java多线程支持引入了同步监视器来解决多线程安全,同步监视器的常用方法就是同步代码块:
[java]viewplaincopy
95Synchronized(obj){
96//...同步代码块
97}
括号中的obj就是同步监视器:
上面的语句表示:
线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。
这就意味着任何时刻只能有一条线程可以获得对同步监视器的锁定,当同步代码块执行结束后,该线程自然释放了对该同步监视器的锁定。
虽然java中对同步监视器使用的对象没有任何要求,但根据同步监视器的目的:
阻止两条线程对同一个共享资源进行并发访问。
所以一般将可能被并发访问的共享资源充当同步监视器。
修改后如下:
[java]viewplaincopy
98publicvoidrun(){
99synchronized(accout){
100//账户余额大于取款金额时
101if(accout.getBalance()>=drawAmount){
102//取款成功
103System.out.println(Thread.currentThread().getName()+accout.getAccoutName()+"取款成功:
吐出钞票:
"+drawAmount);
104//修改余额
105accout.setBalance(accout.getBalance()-drawAmount);
106System.out.println("当前余额为:
"+accout.getBalance());
107}
108//账户金额不够时
109else{
110System.out.println("账户金额不够,您的余额只有"+accout.getBalance());
111}
112}
113}
2.同步方法
(synchronized可以修饰方法,代码块。
不能修饰属性和构造方法)
除了同步代码块外还可以使用synchronized关键字来修饰方法,那么这个修饰过的方法称为同步方法。
对于同步方法来说,无需显式指定同步监视器,同步方法的同步监视器是this,也就是该对象本身,也就是上面TestDraw中定义的Accout类型的acct。
[java]viewplaincopy
114publicvoidrun(){
115draw();
116}
117publicsynchronizedvoiddraw(){
118if(accout.getBalance()>=drawAmount){
119//取款成功
120System.out.println(Thread.currentThread().getName()+accout.getAccoutName()+"取款成功:
吐出钞票:
"+drawAmount);
121//修改余额
122accout.setBalance(accout.getBalance()-drawAmount);
123System.out.println("当前余额为:
"+accout.getBalance());
124}
125//账户金额不够时
126else{
127System.out.println("账户金额不够,您的余额只有"+accout.getBalance());
128}
129}
这里最好是将draw()方法写到Accout中,而不是像之前将取钱内容保存在run方法中,这种做法才更符合面向对象规则中的DDD(DomainDrivenDesign领域驱动设计)
对于可变类的同步会降低程序运行效率。
不要对线程安全类德所有方法进行同步,只对那些会改变共享资源的方法同步。
单线程环境(可以使用线程不安全版本保证性能)多线程环境(线程安全版本)
2.1同步监视器的锁定什么时候释放
A.当前线程的同步方法,同步块执行结束。
当前线程释放同步监视器
B.在同步方法,块中遇到break,return终止了该代码块,方法.释放
C.在代码块,方法中出现Error,Exception
D.执行同步时,程序执行了同步监视器对象的wait()方法,则当前线程暂停,释放
2.2同步监视器的锁定在以下情况不会被释放
A.执行同步时,程序调用Thread.sleep(),Thread.yield()方法来暂停当前线程的执行,不会释放
B.执行同步时,其他线程调用了该线程的suspend方法将该线程挂起,不会释放(但是尽量避免使用suspend和resume来控制线程,容易导致死锁)
3.同步锁(Lock)
在jdk1.5后,java除了上面两种同步代码块和同步方法之外,还提供了一种线程同步机制:
它通过显示定义同步锁对象来实现同步,在这种机制下,同步锁应该使用Lock对象充当。
Lock是控制多个线程对共享资源进行访问的工具,每次只能有一个线程对Lock对象枷锁,线程开始访问共享资源之前应先获得Lock对象。
(特例:
ReadWriteLock锁可能允许对共享资源并发访问)。
在实现线程安全控制中,通常喜欢使用可重用锁(ReentrantLock),使用该Lock对象可以显示的加锁,释放锁。
CODE:
[java]viewplaincopy
130//声明锁对象
131privatefinalReentrantLockrelock=newReentrantLock();
132publicvoidrun(){
133draw();
134}
135publicvoiddraw(){
136//加锁
137relock.lock();
138try{
139//账户余额大于取款金额时
140if(accout.getBalance()>=drawAmount){
141//取款成功
142System.out.println(Thread.currentThread().getName()+accout.getAccoutName()+"取款成功:
吐出钞票:
"+drawAmount);
143//修改余额
144accout.setBalance(accout.getBalance()-drawAmount);
145System.out.println("当前余额为:
"+accout.getBalance());
146}
147//账户金额不够时
148else{
149System.out.println("账户金额不够,您的余额只有"+accout.getBalance());
150}
151}
152//释放锁
153finally{
154relock.unlock();
155}}
总结:
同步方法和同步代码块使用与共享资源相关的,隐式的同步监视器,并且强制要求加锁和释放锁要出现在一个块结构中,而且当获取了多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的范围内释放所有锁。
虽然同步方法,代码块的范围机制使多线程安全编程非常方便,还可以避免很多涉及锁的常见编程错误,但有时也需要以更灵活的方式使用锁。
Lock提供了同步方法,代码块中没有的其他功能(用于非块结构的tryLock方法,获取可中断锁lockInterruptibly方法,获取超时失效锁的tryLock(long,TimeUnit)方法)。
ReentrantLock锁具有重入性,即线程可以对它已经加锁的ReentrantLock锁再次加锁,ReentrantLock对象会维持一个计数器来追踪lock方法的嵌套调用,线程每次调用lock()加锁后,必须显示的调用unlock()释放锁,所以一段被锁保护的代码可以调用另一个被相同锁保护的方法。
4.死锁
当两个线程相互等待对方释放同步监视器的时候就会发生死锁,一旦出现死锁,整个程序既不会发生任何异常,也不会有任何提示,只是所有线程处于阻塞状态,无法继续。
五.线程通信
线程在系统内运行时,线程的调度具有一定的透明性,程序通常无法准确控制线程的轮换执行,可以通过以下方法来保证线程协调运行.
1.线程协调运行
如果对于一些方法是用同步方法或者同步代码块,那么可以调用Object类提供的wait(),notify(),notifyAll()。
这三个不属于Thread,属于Object类,但必须由同步监视器来调用(同步方法的监视器是this:
则需this.wait()....,同步代码块的监视器是括号中的obj.wait());
2.使用条件变量来控制协调
如果程序没有使用sychronized来保证同步,可以使用Lock来保证同步,则系统中就不存在隐式的同步监视器对象,也就不能使用wait,notify,notifyAll来协调了。
[java]viewplaincopy
156privatefinalLocklock=newReentrantLock();
157privatefinalConditioncond=lock.newCondition();
通过上面两行代码,条件Condition实际是绑定在一个Lock对象上的。
相对应的Condition类也有三个方法:
await(),signal(),signalAll()
Account账号类:
代码:
[java]viewplaincopy
158/**账户类,用面向对象的DDD设计模式来设计*/
159/*
160*DDD领域驱动模式,即将每个类都认为是一个完备的领域对象,例如Account代表账户类,那么就应该提供用户账户的相关方法(存,取,转),而不是将
161*setXXX方法暴露出来任人操作。
只要设计到DDD就需要重写equals和hashcode来判断对象的一致性
162*/
163publicclassAccount{
164//账户编码
165privateStringaccountNo;
166//账户余额
167privatedoublebalance;
168//标示账户是否已有存款(此项目为了测试存入款就需要马上取出)
169privatebooleanflag=false;
170//privatefinalLocklock=newReentrantLock();
171//privatefinalConditioncond=lock.newCondition();
172publicAccount(){
173super();
174}
175publicAccount(StringaccountNo,doublebalance){
176super();
177this.accountNo=accountNo;
178this.balance=balance;
179}
180//取款(利用同步方法)
181publicsynchronizedvoiddraw(doubledrawAmount){
182//如果flag为假,没人存款进去,取钱方法(利用wait)阻塞(wait阻塞时,当前线程会释放同步监视器)
183try{
184if(!
flag){
185this.wait();//条件cond.await();
186}
187//否则执行取钱
188else
189{//System.out.println("账户余额:
"+balance);
190System.out.println(Thread.currentThread().getName()+"---->取钱:
"+drawAmount);
191balance-=drawAmount;
192System.out.println("账户余额:
"+balance);
193//设置flag(限定一个操作只能取一次钱)
194flag=false;
195//唤醒其他wait()线程
196this.notifyAll();//cond.signalAll();
197}
198}catch(InterruptedExceptione){
199e.printStackTrace();
200}
201}
202//存款
203publicsynchronizedvoiddeposit(doubledepositAmount){
204//如果flag为真,证明有人存钱了,存钱阻塞
205try{
206if(flag){
207this.wait();//cond.await();
208}
209//否则执行存款
210else
211{//System.out.println("账户余额:
"+balance);
212System.out.println(Thread.currentThread().getName()+"---->存钱:
"+depositAmount);
213balance+=depositAmount;
214System.out.println("账户余额:
"+balance);
215//设置flag(限定一个操作只能取一次钱)
216flag=true;
217//唤醒其他wait()线程
218this.notifyAll();//cond.signalAll();
219}
220}catch(InterruptedExceptione){
221e.printStackTrace();
222}
223}
224//DDD设计模式重写equals和hashcode(判断用户是否一致,只需要判断他们的账号编码就可以了,不需要再判断整个对象,提高性能)
225@Override
226publicinthashCode(){
227returnaccountNo.hashCode();
228}
229@Override
230publicbooleanequals(Objectobj){
231if(obj!
=null&&obj.getClass()==Account.class){
232Accountaccount=(Account)obj;
233returnaccount.getAccountNo().equals(accountNo);
234}
235returnfalse;
236}
237publicStringgetAccountNo(){
238returnaccountNo;
239}
240publicvoidsetAccountNo(StringaccountNo){
241this.accountNo=accountNo;
242}
取钱线程:
[java]viewplaincopy
243publicclassDrawThreadimplementsRunnable{
244/*
245*模拟用户
246*/
247privateAccountaccount;
248//用户取钱数
249privatedoubledrawAmount;
250publicDrawThread(Accountaccount,doubledrawAmount){
251super();
252this.account=account;
253this.drawAmount=drawAmount;
254}
255@Override
256publicvoidrun(){
257//重复10次取钱操作
258for(inti=0;i<10;i++){
259account.draw(drawAmount);
260}
261}
262}
存钱线程:
[java]viewplaincopy
263publicclassDepositThreadimplementsRunnable{
264/*
265
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Java 多线程 学习 笔记