java面向对象精髓编程-chp13多线程_第1页
java面向对象精髓编程-chp13多线程_第2页
java面向对象精髓编程-chp13多线程_第3页
java面向对象精髓编程-chp13多线程_第4页
java面向对象精髓编程-chp13多线程_第5页
已阅读5页,还剩28页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Chp13多线本章导读并发编的支持很好,使用Java语言能够非常简单的写出多线编的代码。多线与并发的概并发与多进windowsWordeclipse写代码,开着一个QQ聊天,开着一个IEWinamp听歌„„这就意味着,在一个操作都需要在CPU上完成运算,也就是说,所有的序运行时,都需要占用CPU。那么,在系统中,是如何做到使用单CPU同时运行多个序的呢?假设我们同时开了多个序:Word,IE,QQ,Winamp,对于操作系统来说,这意味着有四个进要同时运行。为了解决这个问题,计算机规定了:让这四个进轮流使用CPU,CPU上只有一个序在运行。示意图如下: 如上图所示,在某个特定的CPU时间片中,只运行一个序。而操作系统控制CPU,让多个序不停的切换,从而保证多个序能够轮流使用CPU。CPUCPU时间片非常短,每次从这就是单CPU执行多任务的原理:利用很短的CPU时间片运行序,在多个序之间CPU时间片的进行快速切换。可以把这种运行方式总结成为一句话:宏观上并行,微线的概上面所说的,是操作系统与多进的概念。但是,由于Java代码是运行在JVM中的,操作系统打交道,因此,Java语言中没有多进的概念。也就是说,我们无法通过Java代Java中的并发,采用的是线的概念。简单的来说,一个操作系统可以同时运行多个目前为止,我们见到的Java序只有一个线,也就是说,只有一个序执行流。这个流从main方法的第一行开始执行,以main方法的退出作为结束。这个线我们称就是必须要为线写代码。这些代码是说明,启动线之后,这个线完成了什么功能,我其次,为了能够运行,线需要获得CPU。只有获得了CPU之后,线才能真正启动并且执行线的代码。CPU的调度工作是由操作系统来完成的。ThreadRunnableThreadpublicvoidrun()Thread中的run()即可。示例代码如下:classMyThread1extendsThread{publicvoidrun(){for(inti=1;i++){System.out.println(i+}}}classMyThread2extendsThread{publicvoidrun(){for(inti=1;i++){System.out.println(i+"}}}加start()方法,这个方法是从Thread类中继承来的。相关代码如下:publicclasspublicstaticvoidargs[]){Threadt1=newMyThread1();Threadt2=newMyThread2();}}12345678910111213949$$$10001000Threadt1=newMyThread1();Threadt2=new利用new关键字创建出来的,是JVM中的两个对象。这两个对象并不等于系统中的线,是run(),而不要去覆盖start()!调用完start()之后,线就开始真正启动了。可以猜测,在start()内部,会通过JVM跟底层的操作系统进行交互,请求系统创建一个新线。创建完新线之后,这个线执行的另外,当我们对两个对象调用了start方法之后,在我们的JVM中总共有几个线在运t1线:执行MyThread1中的run方法t2线:执行MyThread2中的run方法主线:执行main方法。但是,那样的话,并没有创建新线。例如,如果在主方法中这样来调用:runt1run方法,当t1的run方法返回之后,再执行t2线的run方法。也就是说,在主线运行了这两个run方法,序自始至终只有一个线在执行。Runnable接接口。Runnablejava.lang包中定义,在这个接口中,只包含一个方法:run()方法。void只需要实现这个run方法就可以了。MyThread2MyRunnable2,并且由继承Thread类改为实现Runnable接口。相应代码如下:classMyRunnable2implementsRunnable{publicvoidrun(){for(inti=1;i++){System.out.println(i+"}}}在上面的代码中,对run()方法的实现没有任何修改,而只是改动了第一行:把继承Thread类改成了实现Runnable接口。修改完了线的实现之后,还必须要修改一下创建线的代码。由于MyRunnable2类。换句话说,我们利用MyRunnable2创建出来的不是一个线对象,而是一个所谓的Runnabletarget=newThreadt2=new通过上面的两行代码,我们就可以利用Runnable接口的实现类来创建线对象。创建classMyThread1extendsThread{publicvoidrun(){for(inti=1;i++){System.out.println(i+}}classMyRunnable2implementsRunnable{publicvoidrun(){for(inti=1;i++){System.out.println(i+"}}}publicclasspublicstaticvoidargs[]){Threadt1=newMyThread1();Runnabletarget=newMyRunnable2();Threadt2=newThread(target);}}线的状线状态另外,要注意的是,当一个线调用start()方法之后,由初始状态进入了可运行状态。行状态转换为初始状态。也就是说,一旦调用完了start方法之后,线就进入了可运行状态而不会回到初始状态,因此对象的整个生命周期中,只能调用一次start()方法。如果对同一个线对象多次调用start()方法,会产生一个IllegalStateException异常。的准备,只等着获得CPU来运行。也就是说,线是万事俱备,只欠CPU。运行状态:处于这种状态的线获得了CPU时间片,正在执行代码。由于系统中只有一个CPU,因此,同时只能有一个线处于运行状态。当CPU空闲的时候,操作系统会检查是否有可运行状态的线。如果有一个或多个线处于可运行状态,系统会根据一定的规则挑选一个线,为这个线分配一个CPU时间务同时进行,会为线分配一个CPU时间片。当CPU时间片到期而线没有执行完毕时,线,为其分配CPU时间片。这就是运行状态和可运行状态之间的转换。线:主线。主线的启动没有必要经历初始状态,当我们启动JVM执行序时,JVM会执行某个类的主方法,此时主方法就在主线执行。此后,当主方法结束之后,主线publicclasspublicstaticvoidargs[]){Threadt1=newMyThread1();Runnabletarget=newMyRunnable2();Threadt2=newThread(target);}}中,除了主线之外,还有两个线t1和t2,这两个线还在没有终止,因此序不会终我们之前曾经,序启动之后执行主方法,主方法退出时整个序退出。应当说,这样的说法是确的,必须等序中所有线都进入终止状态之后,整个序才会终止。只不过之前我们写的所有序都只有主线一个线而已,当这个线进入终止状态之后,t1线时,有没有线处于运行状态主线在运行,因此新启动的线只能处于可运行状态。sleep()与阻塞什么是阻塞状态呢?我们知道,如果一个线要进入运行状态,则必须要获得CPU时间片。但是,在某些情况下,线运行不仅需要CPU进行运算,还需要一些别的条件,例况下,即使线获得了CPU时间片,也无法真正运行。因此,在这种情况下,为了能够不让这些线白白占用CPU的时间,会让这些线会进入阻塞状态。最典型的例子就是等待I/O,也就是说,如果线需要与JVM外部进行数据交互的话(例如等待用户输入、读写文件、网络传输等这个时候,当数据传输没有完成时,线即使获得CPU也无法运行,因可以这么来理解:我们可以把CPU当做是银行的柜员,而去银行的准备办理业务的顾客就好比是线。顾客希望能够让银行的柜员为自己办事,就好像线希望获得CPU来运/等待IOstart()方run()退入阻塞状态。顾名思义,sleep方法就是让线进入“睡眠”状态。你可以想象,如果一个publicstaticvoidsleep(longmillis)throws这个方法是一个static方法,也就意味着可以通过类名来直接调用这个方法。sleep方另外,这个方法抛出一个InterruptedException异常,这个异常是一个已检查异常,根据异classMyThread1extendsThread{publicvoidrun(){for(inti=1;i<=1000;i++){System.out.println(i+"$$$");}}}Threadrun方法没有抛出任何异常,根据方法覆盖的要序员只能用try-catch的方法处理。classMyRunnable2implementsRunnable{publicvoidrun(){for(inti=1;i<=1000;System.out.println(i+"}}}112210001000状态,操作系统会从t1和t2中挑选一个线进入运行状态。系统会从可运行状态中挑选一个线进入运行状态。由于可运行状态中只有t2一个线,因此t2线得到运行。入了阻塞状态。此时,t1和t2线都处于阻塞状态,而没有线处于可运行状态以及运行当200毫秒过去之后,t1线的sleep方法返回,此时t1线由阻塞状态进入可运行于可运行状态的t1线进入运行状态。再之后,t2线的sleep方法返回,t2也进入了可运行状态。于是进过多次状态的反复和转换,t1和t2两个线都输出完毕之后,序中所有线都进当然,如果多次运行的话,可以发现,t1和t2两个线的交替输出不是,有可5566778899除了使用sleep()和等待IO之外,还有一个方导致线阻塞,这就是线的classMyThread1extendsThread{publicvoidrun(){for(inti=0;i++){System.out.println(i+}}}classMyThread2extendsThread{publicvoidrun(){for(inti=0;i++){System.out.println(i+"}}}publicclasspublicstaticvoidmain(Stringargs[]){MyThread1t1=newMyThread1();MyThread2t2=newMyThread2();t1.start();}}###classMyThread2extendsThreadpublicvoidfor(inti=0;i++){System.out.println(i+"}}}publicclasspublicstaticvoidmain(Stringargs[]){MyThread1t1=newMyThread1();MyThread2t2=newMyThread2();t2.t=t1;}}也就是让t属性和t1指向同一个对象。这两个线就进入了可运行状态。假设操作系统首先挑选了t1线进入了可运行状态,于是输出若干个“$$$”。经过一段时间之后,由于CPU时间片到期,t1线进入了可运行状态。假设经过了一段时间之后,操作系统选择了t2线进入了可运行状态。进入了t2线就意味着在t2线,调用了t1线的join()方法。调用之后,t2线会进入阻塞状态。行状态此时只有一个t1线,因此这个线会一直占用CPU,直到线代码执行结束。当t1线结束之后,t2才会由阻塞状态进入可运行状态,此时才能够执行t2的代码。因此,从输出结果上来看,会先执行t1线的所有代码,然后再执行t2线的所有代码。部分输012979899要注意的是,我们在t2线调用t1线的join()方法,结果是t2阻塞,直到t1线结束。t2线是join()方法的调用者,而t1线是被调用者,在调用join()方法的过,join()方法。假设顾客到饭店里去吃饭,那么每一线的join()方法,等厨师做完饭了,顾客才能继续下一步:吃饭。在这个例子中,顾客在调用join方法的过,要注意,不能让两个线相互join()。例如,如果一个顾客join()方法,顾客打算等厨师做完饭以后,吃完饭再给钱;而厨join()方法。这样的结果就是两边互相等待,结果谁都无法继续下去。我们拿代码模拟一classMyThread1extendsThread{Threadt;publicvoidrun(){}catch(Exceptione){}for(inti=0;i<100;i++){System.out.println(i+"}}}classMyThread2extendsThread{Threadt;publicvoidrun(){}catch(Exceptione){}for(inti=0;i<100;i++){System.out.println(i+"}}}publicclasspublicstaticvoidmain(Stringargs[]){MyThread1t1=newMyThread1();MyThread2t2=newMyThread2();t2.t=t1;t1.t=t2;}}因为这样的代码在编译和运行时都不会出现任何错误和异常。另一方面,Javajoin()方publicfinalvoidjoin(longmillis)throwsjoin()方法的时候,不会一直处于阻塞状态,而是有一个时间限制。就好像顾客等待厨傻等下去。利用这个方法,修改上面的MyThread1类:classMyThread1extendsThread{Threadt;publicvoidrun(){}catch(Exceptione){}for(inti=0;i<100;i++){System.out.println(i+"}}}线同临界资源与数据不一致本操作为push和pop。push表示把一个元素加入栈顶,pop表示把栈顶元素弹出。classchar[]data={'A','B',''};intindex=2;publicvoidpush(charch){data[index]=ch;index++;}publicpop(){index--data[index]='}publicvoidfor(inti=0;i++){System.out.print(data[i]+}}}publicclasspublicstaticvoidargs[]){MyStackms=newMyStack();}} publicvoidpush(charch){data[index]=ch;index}线这两个类用来MyStack对象。PushThread线负责向栈中添加一个字符,而PopThread线classchar[]data={'A','B',''};intindex=2;publicvoidpush(charch){data[index]=ch;index++;}publicpop(){index--data[index]='}publicvoidfor(inti=0;i++){System.out.print(data[i]+}}}classPopThreadextendsThread{MyStackms;ms){this.ms=}run(){ms.pop();}}classPushThreadextendsThread{MyStackms;ms){this.ms=}run(){);}}publicclasspublicstaticvoidargs[]){MyStackms=newMyStack();Threadt1=newPushThread(ms);Threadt2=newPopThread(ms);}} 注意,这就产生了问题:我们的栈要求先出,栈内原有两个元素A、B,结果先启动了Push线,后启动Pop线之后,却把B元素pop出去,而把C元素保留了。更关键的是,C元素和A元素中有一个空位,也就是说,栈内的元素变得不连续了!我们结合线状态的转换来看这个问题。在主方法中,t1和t2线都被调用了方法被启动。假设t1线先进入运行状态,而t2线保持在可运行状态。此外,要注意的是,t1线和t2线,都包含有一个MyStack类型的。在主方法中,我们只创建了一个MyStack类型通过构造方t1t2ms属性都指向了同一个MyStack在t1线进入运行状态之后,它会调用MyStack类中的push(‘C’)方法。这个在执行这个方法过,会首先修改data数组的值,把下标为2的位置设为C。之后,应当把index加1,但是线调用了sleep()方法,使得t1线进入了阻塞状态。此时,data数组中有3index2dataindex,出现了信息当t1处于阻塞状态的时候,t2线处于可运行状态。于是,操作系统就会选中t2线1data1data2C1的元素B!这样就造成了我们上面运行的结果,破坏012AB2ABC2t1线执行了C字符栈ABC3t1线执行index加ABC2AB2C’012AB2ABC2t1线执行了C字符栈ABC1t1t2CPUAC1t2执行出栈过完AC2t12、index1push()操作;如果这两个由于当t1线没有完成的操作的时候,t2线就开始对MyStack对象进行了,从而就锁的概念时间片到期了,然后接下来换成pop线运行,此时,上一节我们描述的同步问题,同样多小,在大量和重复的过,发生问题几乎是必然的。而同步问题一旦发生,就有可某一天,在下午17:55的时候,电工A接到小区居民:小区中的电压不稳,希望A还是决定去查看一下。为了爬A暂时把小区的电闸给关了,然后再到高空进行电在A在工作过,不知不觉到了18:01,电工B上班了。这个时候,电工B接到小怪不得小区停电呢,谁把闸关了啊。于是,电工B就把电闸打开了。A把伤养得差不多了以后,变电所决定解决吸取教训,争取再也不要我们可以把A和B当做是两个线,而电闸就当做是临界资源。因此,这样就形成了synchronized面我们就来介绍Java中的同步机制以及synchronized关键字。synchronized关键字进行加锁的操作。synchronized关键字有两种用法,我们首先介绍第一种:synchronized+代码块。}synchronized关键字后面跟一个圆括号,括号中的是某一个,这个应当指向某对象的锁标记;而如果t1线在同步代码块中运行,这意味着t1有着obj对象的互斥锁标记;而这个时候如果有一个t2线想要同步代码块,会因为拿不到obj对象的锁标需要注意的是,synchronized与同步代码块是与对象紧密结合在一起的,加锁是对对象}}}假设有一个线t1正在代码块1中运行,那假设另有一个线t2,这个t2线能否进入代码块2呢?能否进入代码块3呢?t2线如果要进入代码块2,也必须要获得obj1对象的锁标记。但是由于这个标记正在t1手中,因此t2线无法获得锁标记,因此t2线无法进入代码块2。但是t2线能够进入代码块3,原因在于:如果要进入代码块3中,要获得的是obj2锁标记,因此能够成功的进入代码块3。这个对象的互斥锁标记,我们完成对MyStack的同步。classchar[]data={'A','B',''};intindex=2;privateObjectlock=newpublicvoidpush(chara[index]=ch;try{index++;}}publicvoidex--;data[index]='}}publicvoidfor(inti=0;i++){System.out.print(data[i]+}}}start()方run()退CPU时间片,从而运行代码。对象的互斥锁标记。由于此时lock对象的锁标记没有分配给其他线,因此这个锁标记被分配给t1线。当t1线调用sleep()方法之后,t1进入阻塞状态,于是t2线开始运行。之后,当t1线sleep()方法结束,t1重新进入可运行状态→运行状态,执行完了整个是t2线就由锁池状态转为了可运行状态。从上面的分析我们可以看出,虽然MyStack对象还是被两个线t1、t2同时,但是由于对lock对象加锁的原因,当t1线执行push方法执行到一半进入阻塞状态时,t2012AB2ABC2t1线执行了C字符栈ABC2t2ABC3t1线执行index加ABC2码AB2C’同步方法poppushMyStack对象(也就是所谓classchar[]data={'A','B',''};intindex=2;publicvoidpush(chara[index]=ch;try{index++;}}publicvoidex--;data[index]='}}publicvoidfor(inti=0;i++){System.out.print(data[i]+}}}classchar[]data={'A','B',''};intindex=2;publicsynchronizedvoidpush(charch){data[index]=ch;index++;}publicsynchronizedvoidpop(){index--;data[index]='}publicvoidfor(inti=0;i++){System.out.print(data[i]+}}}我们举例来说:假设有一个MyStack对象ms,一个线t1正在pop方法,这意味必须要获得ms对象的互斥锁标记。由于这个锁标记正在t1线手中,因此t2线无法获得,从而t2线就无法push方法。因此,这是一个结论: wait死}}...//2}}假设现在有两个线,t1线运行到了//1的位置,而t2线运行到了//2的位置,接此时,a对象的锁标记被t1线获得,而b对象的锁标记被t2线获得。对于t1线而言,为了进入对b加锁的同步代码块,t1线必须获得b对象的锁标记。由于b对象的锁标记被t2线获得,t1线无法获得这个对象的锁标记,因此它会进入b对象的锁池,等待b对象锁标记的释放。而对于t2线而言,由于要进入对a加锁的同步代码块,由于a对象的锁标记在t1线手中,因此t2线会进入a对象的锁池。此时,t1线在等待b对象锁标记的释放,而t2线在等待a对象锁标记的释放。由waitJava中,每一个对象都有两个方法:waitnotify方法。这两个方法是定义在Object类中的方法。对某个对象调用wait()方法,表明让线暂时释放该对象的锁标记。}}...//2}}这样的代码改完之后,在//1后面,t1线就会调用a对象的wait方法。此时,t1线会暂时释放自己拥有的a对象的锁标记,而进入另外一个状态:等待状态。要注意的是,如果要调用一个对象的wait方法,前提是线已经获得这个对象的锁标记。如果在没有获得对象锁标记的情况下调用wait方法,则会产生异常。a对象的锁标记被释放,因此,t2aaa.notify()wait方法相对那么t2线唤醒t1线之后,t1线处于什么状态呢?由于t1线唤醒之后还要在锁的同步代码块,因此此

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论