●heigth=60width=200>
●
●
●上面这个例子是通过每隔1秒种就执行线程的刷新画面功能,显示当前的时间;看起来的效果就是一个时钟,每隔1秒就变化一次。
由于采用的是实现接口Runnable的方式,所以该类Clock还继承了Applet,Clock就可以Applet的方式运行。
幻灯片15
●采用间接创建线程的原因:
●第一个理由是我们并不改变线程本身的性质,仅覆盖run方法,并没有增加新的功能,因此将Thread扩展子类并不恰当,这不太符合类扩展规范。
●第二个理由是:
如果实现Runnable接口,它可能使我们所设计的类扩展其它类型而变得更为有用。
幻灯片16
8.1.3线程的生命周期
●新建的线程在一个完整的生命周期中通常要经历如下5种状态。
●①创建状态
●使用new运算符创建一个线程后,该线程仅仅是一个空对象,系统没有分配资源,称该线程处于创建状态(newthread)。
●②可运行状态
●使用start()方法启动一个线程后,系统为该线程分配了除CPU外的所需资源,使该线程处于可运行状态(Runnable)。
幻灯片17
●③运行中状态
●Java运行系统通过调度选中一个Runnable的线程,使其占有CPU并转为运行中状态(Running)。
此时,系统真正执行线程的run()方法。
●④阻塞状态
●一个正在运行的线程因某种原因不能继续运行时,进入阻塞状态(Blocked)。
●⑤死亡状态
●线程结束后是死亡状态(Dead)。
幻灯片18
8.1.4线程的调度与优先级
1.线程的调度
●Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。
线程调度器按照线程的优先级决定应调度哪些线程来执行。
●线程调度器按线程的优先级高低选择高优先级线程(进入运行中状态)执行,同时线程调度是抢先式调度,即如果在当前线程执行过程中,一个更高优先级的线程进入可运行状态,则这个线程立即被调度执行。
幻灯片19
●抢先式调度又分为:
时间片方式和独占方式。
●在时间片方式下,当前活动线程执行完当前时间片后,如果有其他处于就绪状态的相同优先级的线程,系统会将执行权交给其他就绪态的同优先级线程;当前活动线程转入等待执行队列,等待下一个时间片的调度。
●在独占方式下,当前活动线程一旦获得执行权,将一直执行下去,直到执行完毕或由于某种原因主动放弃CPU,或者是有一高优先级的线程处于就绪状态。
幻灯片20
yield
1.生产,出产,带来2.屈服,放弃;不再反对,让出
●下面几种情况下,当前线程会放弃CPU:
●①线程调用了yield()或sleep()方法主动放弃;
●②由于当前线程进行I/O访问,外存读写,等待用户输入等操作,导致线程阻塞;或者是为等候一个条件变量,以及线程调用wait()方法;
●③抢先式系统下,由高优先级的线程参与调度;时间片方式下,当前时间片用完,由同优先级的线程参与调度。
幻灯片21
2.线程的优先级
●线程的优先级用数字来表示,范围从1到10,即Thread.MIN_PRIORITY到Thread.MAX_PRIORITY。
一个线程的缺省优先级是5,即Thread.NORM_PRIORITY。
下述方法可以对优先级进行操作:
● intgetPriority();//得到线程的优先级
● voidsetPriority(intnewPriority);
● //当线程被创建后,可通过此方法改变线程的优先级
幻灯片22
例:
生成三个不同线程,其中一个线程在最低优先级下运行,而另两个线程在最高优先级下运行。
importjava.io.*;
publicclasstest{
publicstaticvoidmain(String[]args){
Threadt1=newMyThread("T1");
t1.setPriority(Thread.MIN_PRIORITY);//设置优先级为最小
t1.start();
Threadt2=newMyThread("T2");
t2.setPriority(Thread.MAX_PRIORITY);//设置优先级为最大
t2.start();
Threadt3=newMyThread("T3");
t3.setPriority(Thread.MAX_PRIORITY);//设置优先级为最大
t3.start();
}
}
幻灯片23
classMyThreadextendsThread{
Stringmessage;
MyThread(Stringmessage){
this.message=message;
}
publicvoidrun(){
for(inti=0;i<3;i++)
System.out.println(message+""+getPriority());
//获得线程的优先级
}
}
运行结果:
T11
T210
T210
T210
T310
T310
T310
T11
T11
注意:
并不是在所有系统中运行Java程序时都采用时间片策略调度线程,所以一个线程在空闲时应该主动放弃CPU,以使其他同优先级和低优先级的线程得到执行。
幻灯片24
8.2多线程的互斥与同步
●前面所提到的线程都是独立的,而且异步执行,也就是说每个线程都包含了运行时所需要的数据或方法,而不需要外部的资源或方法,也不必关心其它线程的状态或行为。
但是经常有一些同时运行的线程需要共享数据,此时就需考虑其他线程的状态和行为,否则就不能保证程序的运行结果的正确性。
幻灯片25
例:
classStack{
intidx=0;//堆栈指针的初始值为0
char[]data=newchar[6];//堆栈有6个字符的空间
publicvoidpush(charc){//压栈操作
data[idx]=c;//数据入栈
idx++;//指针向上移动一位
}
publiccharpop(){//出栈操作
idx--;//指针向下移动一位
returndata[idx];//数据出栈
}
}
幻灯片26
●两个线程A和B在同时使用Stack的同一个实例对象,A正在往堆栈里push一个数据,B则要从堆栈中pop一个数据。
如果由于线程A和B在对Stack对象的操作上的不完整性,会导致操作的失败,具体过程如下所示:
●①操作之前
● data=|p|q|||||idx=2
●②A执行push中的第一个语句,将r推入堆栈;
● data=|p|q|r||||idx=2
●③A还未执行idx++语句,A的执行被B中断,B执行pop方法,返回q:
● data=|p|q|r||||idx=1
●④A继续执行push的第二个语句:
● data=|p|q|r||||idx=2
●最后的结果相当于r没有出栈。
产生这种问题的原因在于对共享数据访问的操作的不完整性。
幻灯片27
8.2.1互斥锁
●为解决操作的不完整性问题,在Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
每个对象都对应于一个可称为"互斥锁"的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
关键字synchronized来与对象的互斥锁联系。
当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。
幻灯片28
publicvoidpush(charc){
synchronized(this){//this表示Stack的当前对象
data[idx]=c;
idx++;
}
}
publiccharpop(){
synchronized(this){//this表示Stack的当前对象
idx--;
returndata[idx];
}
}
幻灯片29
●synchronized除了象上面讲的放在对象前面限制一段代码的执行外,还可以放在方法声明中,表示整个方法为同步方法。
● publicsynchronizedvoidpush(charc){
● …
● }
●如果synchronized用在类声明中,则表明该类中的所有方法都是synchronized的。
幻灯片30
8.2.2多线程的同步
●我们将通过多线程同步的模型:
生产者-消费者问题来说明怎样实现多线程的同步。
●我们把系统中使用某类资源的线程称为消费者,产生或释放同类资源的线程称为生产者。
在下面的Java的应用程序中,生产者线程向文件中写数据,消费者从文件中读数据,这样,在这个程序中同时运行的两个线程共享同一个文件资源。
通过这个例子我们来了解怎样使它们同步。
幻灯片31
例:
importjava.io.*;
publicclassTest{
publicstaticvoidmain(Stringargs[]){
SyncStackstack=newSyncStack();
//下面的消费者类对象和生产者类对象
所操作的是同一个同步堆栈对象
Runnablesource=newProducer(stack);
Runnablesink=newConsumer(stack);
Threadt1=newThread(source);//线程实例化
Threadt2=newThread(sink);//线程实例化
t1.start();//线程启动
t2.start();//线程启动
}
}
幻灯片32
classSyncStack{//同步堆栈类
privateintindex=0;//堆栈指针初始值为0
privatechar[]buffer=newchar[6];//堆栈有6个字符的空间
publicsynchronizedvoidpush(charc){//加上互斥锁
while(index==buffer.length){//堆栈已满,不能压栈
try{
this.wait();//等待,直到有数据出栈
}catch(InterruptedExceptione){}
}
this.notify();//通知其它线程把数据出栈
buffer[index]=c;//数据入栈
index++;//指针向上移动
}
幻灯片33
●publicsynchronizedcharpop(){//加上互斥锁
●while(index==0){//堆栈无数据,不能出栈
●try{
●this.wait();//等待其它线程把数据入栈
●}catch(InterruptedExceptione){}
●}
●this.notify();//通知其它线程入栈
●index--;//指针向下移动
●returnbuffer[index];//数据出栈
●}
●}
幻灯片34
classProducerimplementsRunnable{//生产者类
SyncStacktheStack;//生产者类生成的字母都保存到同步堆栈中
publicProducer(SyncStacks){
theStack=s;
}
publicvoidrun(){
charc;
for(inti=0;i<20;i++){
c=(char)(Math.random()*26+'A');//随机产生20个字符
theStack.push(c);//把字符入栈
System.out.println("Produced:
"+c);//打印字符
try{
Thread.sleep((int)(Math.random()*1000));
/*每产生一个字符线程就睡眠*/
}catch(InterruptedExceptione){}
}
}
}
幻灯片35
classConsumerimplementsRunnable{//消费者类
SyncStacktheStack;//消费者类获得的字符都来自同步堆栈
publicConsumer(SyncStacks){
theStack=s;
}
publicvoidrun(){
charc;
for(inti=0;i<20;i++){
c=theStack.pop();//从堆栈中读取字符
System.out.println("Consumed:
"+c);//打印字符
try{
Thread.sleep((int)(Math.random()*1000));
/*每读取一个字符线程就睡眠*/
}catch(InterruptedExceptione){}
}
}
}
程序执行结果:
Produced:
P
Consumed:
P
Produced:
D
Consumed:
D
Produced:
V
Consumed:
V
Produced:
D
Consumed:
D
Produced:
N
Consumed:
N
Produced:
W
Consumed:
W
幻灯片36
●类Producer是生产者模型,其中的run()方法中定义了生产者线程所做的操作,循环调用push()方法,将生产的20个字母送入堆栈中,每次执行完push操作后,调用sleep()方法睡眠一段随机时间,以给其他线程执行的机会。
类C