System.out.println(getName()+"减去"+getInt);
try{
Thread.sleep
(1);
}catch(InterruptedExceptione){
e.printStackTrace();
}
count-=getInt;
System.out.println(getName()+"减去"+getInt+"后,count还剩下:
"+count);
}else{
System.out.println(getName()+"没有足够的count能够被减掉");
}
}
}
}
多运行几次观察输出结果
Thread-0减去90
Thread-1减去80
Thread-0减去90后,count还剩下:
-70
Thread-1减去80后,count还剩下:
-70
这很明显不是我们想要的结果。
UnsafeThreadTest3类中有一个全局变量count,在MyThread中,接收一个getInt整数,在run方法中,如果这个getInt小于count,我们就用count减去getInt。
如果getInt大于count,那么我们打印“没有足够的count能够被减掉”。
但就在第一个线程执行的时候,传入90。
这个时候,count的值是100,所以能够减去90(但还没有减)。
但就在这个时候,另外一个线程也过来了,传入80,也小于100,所以两边都满足,最后一起减掉了。
所以程序出现了负数。
这样显然是不可以的。
这个案例可以延伸到银行取款中来。
packagecom.hzgg.test2;
publicclassAccount{
privateStringcardNo;
privatedoublemoney;
publicAccount(StringcardNo,doublemoney){
this.cardNo=cardNo;
this.money=money;
}
publicStringgetCardNo(){
returncardNo;
}
publicvoidsetCardNo(StringcardNo){
this.cardNo=cardNo;
}
publicdoublegetMoney(){
returnmoney;
}
publicvoidsetMoney(doublemoney){
this.money=money;
}
}
packagecom.hzgg.test2;
publicclassATMextendsThread{
//模拟账户
privateAccountaccount;
//取多少钱
privatedoublecaMoney;
publicATM(Stringname,Accountaccount,doublecaMoney){
super(name);
this.account=account;
this.caMoney=caMoney;
}
@Override
publicvoidrun(){
//如果账户余额大于取钱的数目
if(account.getMoney()>caMoney){
System.out.println(getName()+"取钱成功!
共取:
"+caMoney);
try{
Thread.sleep
(2);
}catch(InterruptedExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
account.setMoney(account.getMoney()-caMoney);
System.out.println("余额是:
"+account.getMoney());
}else{
System.out.println(getName()+"余额不足!
取款失败");
}
}
publicstaticvoidmain(String[]args){
Accounta=newAccount("1111222233334444",6000);
newATM("云飞日月",a,5000).start();
newATM("刘欢",a,5000).start();
}
}
这几个案例让我们见到了线程存在的问题,如果不加以处理,1万次中出现1次,那也是错误的。
10.5.2同步方法
synchronized关键字也可以放到方法上,这样整个方法就是同步的。
同步监视器就是类对象本身this。
注意:
synchronized关键字可以修饰方法、代码块,但是不能修饰构造方法和属性。
线程要进入同步代码块或同步方法中,必须先获得同步监视器的锁定。
也就是说必须先拿到锁,然后进入方法。
那么什么时候释放锁呢?
1.方法执行结束
2.在方法中遇到Exception,导致异常
3.程序中遇到了退出程序的代码,比如return
4.程序执行了同步监视器对象的wait()方法
10.6waitnotify
当线程A运行过程中遇到不满足的条件需要等待,等待另外的线程B去更改系统状态。
当B更改系统状态后,唤醒等待线程A。
等待线程A查看是否满足条件,如果满足则继续执行,不满足则继续等待。
举个例子。
在ATM中,有两个线程。
一个用来存款,一个用来提款。
当提款的线程提款时,发现余额不足,那么它等待有线程过来存款。
一旦有线程存款,那么马上唤醒这个提款的线程。
提款的线程继续查看是否余额大于提款额度,如果大于,则提款,否则继续等待。
这个功能的实现可以借助于Object类的waitnotify和notifyAll方法,注意,这三个方法不是Thread类的方法,而是Object类方法。
另外,要使用这些方法,必须获得同步监视器对象。
这两个线程是交替执行;wait方法只能在获取同步监视锁的方法中或代码块中才能调用同步监视锁既是synchronized
packagecom.hzgg.test2;
publicclassTestThreadWait{
publicstaticvoidmain(String[]args){
TestThreadWaitttw=newTestThreadWait();
ttw.init();
}
Vv=newV();
privatevoidinit(){
//TODOAuto-generatedmethodstub
newThread(newRunnable(){
@Override
publicvoidrun(){
//TODOAuto-generatedmethodstub
while(true){
v.f1();
}
}
}).start();
newThread(newRunnable(){
@Override
publicvoidrun(){
//TODOAuto-generatedmethodstub
while(true){
v.f2();
}
}
}).start();
}
classV{
booleanflag=true;
publicsynchronizedvoidf1(){
while(flag){
try{
wait();
}catch(InterruptedExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
}
flag=true;
notifyAll();
System.out.println("我是线程1AAAAAAAAAA");
}
publicsynchronizedvoidf2(){
while(!
flag){
try{
wait();
}catch(InterruptedExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
}
flag=false;
notifyAll();
System.out.println("我是线程2BBBBBBBBBB");
}
}
}
运行结果部分:
我是线程1AAAAAAAAAA
我是线程2BBBBBBBBBB
我是线程1AAAAAAAAAA
我是线程2BBBBBBBBBB
我是线程1AAAAAAAAAA
我是线程2BBBBBBBBBB
在这段代码中,我们开启了两个线程,线程中的run方法调用了V类对象的f1方法。
在f1方法中,首先必须获得同步监视器对象,也就是必须有synchronized才可以使用wait和notifyAll。
第一个线程进入f1方法,发现flag为false,没有进入if代码块,将flag设置为true(这时候另外一个线程可能已经进入f1方法,发现flag为false,于是进入wait状态),同时唤醒正在wait状态的所有线程(这里使用的notifyAll)。
这时候,进入wait状态的线程会被唤醒,继续检查是否满足条件,如果满足条件则运行,否则继续等待。
假如是三个线程没人一下的运行,代码如下
packagecom.hzgg.test2;
publicclassTestThreadWait2{
publicstaticvoidmain(String[]args){
TestThreadWait2tt=newTestThreadWait2();
tt.go();
}
Vv=newV();
privatevoidgo(){
//TODOAuto-generatedmethodstub
newThread(newRunnable(){
@Override
publicvoidrun(){
while(true){
v.f1();
}
}
}).start();
newThread(newRunnable(){
@Override
publicvoidrun(){
//TODOAuto-generatedmethodstub
while(true){
v.f2();
}
}
}).start();
newThread(newRunnable(){
@Override
publicvoidrun(){
//TODOAuto-generatedmethodstub
while(true){
v.f3();
}
}
}).start();
}
classV{
publicinti=1;
publicsynchronizedvoidf1(){
while(i!
=1){
try{
wait();
}catch(InterruptedExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
}
i=2;
notifyAll();
System.out.println("AAAAAAAAAAAAAAAAAAAA");
}
publicsynchronizedvoidf2(){
while(i!
=2){
try{
wait();
}catch(InterruptedExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
}
i=3;
notifyAll();
System.out.println("BBBBBBBBBBBBBB");
}
publicsynchronizedvoidf3(){
while(i!
=3){
try{
wait();
}catch(InterruptedExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
}
i=1;
notifyAll();
System.out.println("CCCCCCCCCCCCCCCCCCc");
}
}
}
AAAAAAAAAAAAAAAAAAAA
BBBBBBBBBBBBBB
CCCCCCCCCCCCCCCCCCc
AAAAAAAAAAAAAAAAAAAA
BBBBBBBBBBBBBB
CCCCCCCCCCCCCCCCCCc
10.6.1生产者消费者问题
很多情况下,我们需要这样的模型。
大家可以想象一下吃自助餐。
在自助餐的公共区域有很多食物,我们(消费者)可以去挑选食物。
然而,这时候食物被我们选没了,于是大家伙都等待。
在等待什么呢?
等待厨师做出新的一批食物放置上来,我们就可以继续选择我们喜爱的食物。
同样,如果反过来理解的话也可以。
我们可以制造一些请求,这些请求放到一个队列中。
队列的另一端会处理请求。
如果队列中有请求,那么处理请求,没有的话等待请求的到来。
我们在队列中放入请求的时候,如果队列满了,我们就等待处理请求处理完一个请求,于是我们可以放入新的请求。
我们用代码来模拟第二种情况:
Request:
请求对象
RequestContainer:
存放请求的容器
ProcessRequestThread:
处理请求的线程
MakeRequestThread:
制造请求的线程
代码
Request:
请求对象
产品类
packagecom.hzgg.threadSafe;
publicclassProduct{
privateintid;
privateStringname;
publicProduct(intid,Stringname){
this.id=id;
this.name=name;
}
publicintgetId(){
returnid;
}
publicvoidsetId(intid){
this.id=id;
}
publicStringgetName(){
returnname;
}
publicvoidsetName(Stringname){
this.name=name;
}
}
RequestContainer:
存放请求的容器
桌子类
packagecom.hzgg.threadSafe;
importjava.util.LinkedList;
publicclassTable{
//容器
LinkedListlist=newLinkedList();
publicintmax=10;
/**
*往桌子上方产品
*@paramp
*/
publicsynchronizedvoidput(Productp){
while(size()==max){
try{
wait();
}catch(InterruptedExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
}
list.add(p);
n