java多线程教程.docx
《java多线程教程.docx》由会员分享,可在线阅读,更多相关《java多线程教程.docx(17页珍藏版)》请在冰豆网上搜索。
java多线程教程
第七章多线程机制
【课前思考】
什么是线程?
它和进程有什么区别?
适用方向是什么?
Java的线程是如何实现的?
Java的线程是如何调度的?
Java中的多线程有什么特点?
同步和互斥的原理是如何实现的?
【学习目标】
学习java中线程的使用,掌握线程的调度和控制方法,清楚地理解多线程的互斥和同步的实现原理,以及多线程的应用。
【学习指南】
掌握线程之间的相互调度关系,尤其是通过线程睡眠来使其它线程获得执行机会的机制,以及互斥和同步的实现机制。
【难重点】
●多线程的调度和控制。
●多线程的互斥和同步。
【知识点】
线程简介
多线程的互斥与同步
【内容】
第一节线程的概念
随着计算机的飞速发展,个人计算机上的操作系统也纷纷采用多任务和分时设计,将早期只有大型计算机才具有的系统特性带到了个人计算机系统中。
一般可以在同一时间内执行多个程序的操作系统都有进程的概念。
一个进程就是一个执行中的程序,而每一个进程都有自己独立的一块内存空间、一组系统资源。
在进程概念中,每一个进程的内部数据和状态都是完全独立的。
Java程序通过流控制来执行程序流,程序中单个顺序的流控制称为线程,多线程则指的是在单个程序中可以同时运行多个不同的线程,执行不同的任务。
多线程意味着一个程序的多行语句可以看上去几乎在同一时间内同时运行。
线程与进程相似,是一段完成某个特定功能的代码,是程序中单个顺序的流控制;但与进程不同的是,同类的多个线程是共享一块内存空间和一组系统资源,而线程本身的数据通常只有微处理器的寄存器数据,以及一个供程序执行时使用的堆栈。
所以系统在产生一个线程,或者在各个线程之间切换时,负担要比进程小的多,正因如此,线程被称为轻负荷进程(light-weightprocess)。
一个进程中可以包含多个线程。
程序(program)是对数据描述与操作的代码的集合,是应用程序执行的脚本
进程(process)是程序的一次执行过程,是系统运行程序的基本单位
多任务(multitask)是指在一个系统中可以同时运行多个程序,即有多个独立运行的任务,每个任务对应一个进程
同进程一样,一个线程也有从创建、运行到消亡的过程,称为线程的生命周期。
用线程的状态(state)表明线程处在生命周期的哪个阶段。
线程有创建、可运行、运行中、阻塞、死亡五中状态。
通过线程的控制与调度可使线程在这几种状态间转化
每个程序至少自动拥有一个线程,称为主线程。
当程序加载到内存时,启动主线程。
要加载其他线程,程序就要使用Runnable接口和Thread类
第二节Runnable接口与Thread类
ØRunnable接口
Runnable接口中声明了一个run方法
publicvoidrun()
Runnable接口中的run方法只是一个未实现的方法。
一个线程对象必须实现run方法完成线程的所有活动,已实现的run方法称为该对象的线程体。
任何实现Runnable接口的对象都可以作为一个线程的目标对象.
ØThread类
Thread类将Runnable接口中的run方法实现为空方法,并定义许多用于创建和控制线程的方法。
格式为:
publicclassThreadextendsObjectimplementsRunnable
构造方法:
publicThread()
publicThread(Stringname)
publicThread(Runnabletarget)
publicThread(Runnabletarget,Stringname)
publicThread(ThreadGroupgroup,Runnabletarget)
publicThread(ThreadGroupgroup,Stringname)
publicThread(ThreadGroupgroup,Runnabletarget,Stringname)
Thread类的静态方法:
publicstaticThreadcurrentThread()//返回当前执行线程的引用对象
publicstaticintactiveCount()//返回当前线程组中活动线程个数
publicstaticenumerate(Thread[]tarray)//将当前线程组中的活动线程拷贝到tarray数组中,包括子线程
Thread类的实例方法:
publicfinalStringgetName()//返回线程名
publicfinalvoidsetName(Stringname)//设置线程的名字为name
publicvoidstart()//启动已创建的线程对象
publicfinalbooleanisAlive()//返回线程是否启动的状态
publicfinalThreadGroupgetThreadGroup()//返回当前线程所属的线程组名
publicStringtoString()//返回线程的字符穿信息
例7.1继承Thread类创建线程
本例淙通过继承Thread类来创建线程的方法。
类Thread1声明为Thread的子类,它的构造方法定义线程名和起始参数。
程序如下:
publicclassThread1extendsThread
{
intk=0;
publicThread1(Stringname,intk)
{
super(name);
this.k=k;
}
publicvoidrun()//覆盖run方法的线程体
{
inti=k;
System.out.println();
System.out.print(getName()+":
");
while(i<50)
{
System.out.print(i+"");
i+=2;
}
System.out.println(getName()+"end!
");
}
publicstaticvoidmain(Stringargs[])
{
Thread1t1=newThread1("Thread1",1);//创建线程对象
Thread1t2=newThread1("Thread2",2);
t1.start();//启动执行线程
t2.start();
System.out.println("activeCount="+t2.activeCount());
}
}
程序运行结果:
activeCount=3
Thread1:
13579111315171921232527
Thread2:
246810121416182022242628303234
36384042444648Thread2end!
2931333537394143454749Thread1end!
Ø两种创建线程方法的比较
比较两者的特点和应用领域:
直接继承线程Thread类。
该方法编写简单,可以直接操作线程,适用于单重继承情况,因而不能在继承其他类
实现Runnable接口。
当一个线程已继承了另一个类时,就只能用实现Runnable接口的方法来创建线程,且便于保持程序风格的一致性。
Ø线程组
每个线程都是一个线程组的成员,线程组把多个线程集成为一个对象,通过线程组可以同时对其中的多个线程进行操作,如启动一个线程组的所有线程等。
Java的线程组由java.lang包中的Thread——Group类实现。
ThreadGroup类用来管理一组线程,包括:
线程的数目,线程间的关系,线程正在执行的操作,以及线程将要启动或终止时间等。
线程组还可以包含线程组。
在Java的应用程序中,最高层的线程组是名位main的线程组,在main中还可以加入线程或线程组,在mian的子线程组中也可以加入线程和线程组,形成线程组和线程之间的树状继承关系
第三节线程的控制与调度
Ø线程的生命周期
线程的状态表示线程正在进行的活动以及在此时间段内所能完成的任务。
线程有创建、可运行、运行中、阻塞、死亡五中状态。
一个具有生命的线程,总是处于这五种状态之一。
创建状态
使用new运算符创建一个线程后,该线程仅仅是一个空对象,系统没有分配资源,称该线程处于创建状态(newthread)
可运行状态
使用start()方法启动一个线程后,系统为该线程分配了除CPU外的所需资源,使该线程处于可运行状态(Runnable)
运行中状态
Java运行系统通过调度选中一个Runnable的线程,使其占有CPU并转为运行中状态(Running)。
此时,系统真正执行线程的run()方法。
阻塞状态
一个正在运行的线程因某种原因不能继续运行时,进入阻塞状态(Blocked)
死亡状态
线程结束后是死亡状态(Dead)
Ø线程调度与优先级
线程的调度模型
同一时刻如果有多个线程处于可运行状态,则他们需要排队等待CPU资源。
此时每个线程自动获得一个线程的优先级(priority),优先级的高低反映线程的重要或紧急程度。
可运行状态的线程按优先级排队,线程调度依据优先级基础上的“先到先服务”原则。
线程调度管理器负责线程排队和CPU在线程间的分配,并由线程调度算法进行调度。
当线程调度管理器选种某个线程时,该线程获得CPU资源而进入运行状态。
线程调度是先占式调度,即如果在当前线程执行过程中一个更高优先级的线程进入可运行状态,则这个线程立即被调度执行。
先占式调度分为:
独占式和分时方式。
独占方式下,当前执行线程将一直执行下去,直到执行完毕或由于某种原因主动放弃CPU,或CPU被一个更高优先级的线程抢占
分时方式下,当前运行线程获得一个时间片,时间到时,即使没有执行完也要让出CPU,进入可运行状态,等待下一个时间片的调度。
系统选中其他可运行状态的线程执行
分时方式的系统使每个线程工作若干步,实现多线程同时运行
线程的优先级用1~10表示,1表示优先级最高,默认值是5。
每个优先级对应一个Thread类的公用静态常量。
如:
publicstaticfinalintNORM_PRIORITY=5
publicstaticfinalintMIN_PRIORITY=1
publicstaticfinalintMAX_PRIORITY=10
Ø改变线程状态
1)线程睡眠sleep()
publicstaticvoidsleep(longmillis)throwInterruptedException
当前线程睡眠(停止执行)若干豪秒,线程由运行中的状态进入不可运行状态,睡眠时间过后进程再进入可运行状态。
2)暂停线程yield()
publicstaticvoidyield()
yield()暂停当前线程执行,允许其他线程执行。
该线程仍处于可运行状态,不转为阻塞状态。
此时,系统选择其他同优先级线程执行,若无其他同优先级程,则选中该线程继续执行。
3)连接线程join()
join()方法使当前暂停执行,等待调用该方法的线程结束后再继续执行本线程。
它有三种调用方法:
publicfinalvoidjoin()throwsInterruptedException
publicfinalvoidjoin(longmills)throwsInterruptedException
publicfinalvoidjoin(longmills,intnanos)throwsInterruptedException
等待调用该方法的线程结束,或者最多等待millis毫秒+nanos纳秒后,再继续执行本线程。
如果需要在一个线程中等待,直到另一个线程消失,可以调用join()方法。
如果当前线程另一线程中断,join()方法会抛出InterruptedException异常。
4)中断线程interrupt()
publicvoidinterrupt()
publicbooleanisInterrupted()
publicstaticbooleaninterrupted()
interrupt()方法为线程设置一个中断标记,以便于run()方法运行时使用isInterrupted()方法能够检测到,此时,线程在sleep()方法抛出一个Interr——uptedException异常,然后捕获这个异常以处理超时。
例7.4改变线程状态。
本例演示线程对象的生命周期从创建到结束的过程,其间使用new()、start()、sleep()、interrupt()等方法改变线程的状态。
本例综合运用内部类、图形界面、线程等多方面技术实现设计思想。
程序如下:
importjava.awt.*;
importjava.awt.event.*;
publicclassWelcomeextendsWindowAdapterimplementsActionListener
{
Framef;
staticWelcome.Thread3wt1,wt2;
publicstaticvoidmain(Stringarg[])
{
Welcomew=newWelcome();
w.display();
wt1=w.newThread3("Welcome!
");
wt2=w.newThread3("Howareyou?
");
wt2.start();
wt2.setButton();//设置按钮状态
}
publicvoiddisplay()
{
f=newFrame("Welcome");
f.setSize(400,240);
f.setLocation(200,140);
f.setBackground(Color.lightGray);
f.setLayout(newGridLayout(4,1));
f.addWindowListener(this);
f.setVisible(true);
}
publicclassThread3extendsThread
{
Panelp1;
Labellb1;
TextFieldtf1,tf2;
Buttonb1,b2;
intsleeptime=(int)(Math.random()*100);
publicThread3(Stringstr)
{
super(str);
for(inti=0;i<100;i++)
str=str+"";
tf1=newTextField(str);
f.add(tf1);
p1=newPanel();
p1.setLayout(newFlowLayout(FlowLayout.LEFT));
lb1=newLabel("sleep");
tf2=newTextField(""+sleeptime);
p1.add(lb1);
p1.add(tf2);
b1=newButton("启动");
b2=newButton("中断");
p1.add(b1);
p1.add(b2);
b1.addActionListener(newWelcome());
b2.addActionListener(newWelcome());
f.add(p1);
f.setVisible(true);
}
publicvoidrun()
{
Stringstr;
while(this.isAlive()&&!
this.isInterrupted())
{//线程活动且没中断时
try
{
str=tf1.getText();
str=str.substring
(1)+str.substring(0,1);
tf1.setText(str);
this.sleep(sleeptime);
}
catch(InterruptedExceptione)
{//中断时抛出
System.out.println(e);
break;//退出循环
}
}
}
publicvoidsetButton()//设置按钮状态
{
if(this.isAlive())b1.setEnabled(false);
if(this.isInterrupted())b2.setEnabled(false);
}
}//线程
publicvoidwindowClosing(WindowEvente)
{
System.exit(0);
}
publicvoidactionPerformed(ActionEvente)
{//单击按钮时触发
if((e.getSource()==wt1.b1)||(e.getSource()==wt1.b2))
actionPerformed(e,wt1);
if((e.getSource()==wt2.b1)||(e.getSource()==wt2.b2))
actionPerformed(e,wt2);
}
publicvoidactionPerformed(ActionEvente,Thread3wt1)
{//重载
if(e.getSource()==wt1.b1)//启动
{
wt1.sleeptime=Integer.parseInt(wt1.tf2.getText());
wt1.start();
}
if(e.getSource()==wt1.b2)//中断
wt1.interrupt();
wt1.setButton();//设置按钮状态
}
}
第四节线程的同步机制
前面所提到的线程都是独立的,而且异步执行,也就是说每个线程都包含了运行时所需要的数据或方法,而不需要外部的资源或方法,也不必关心其它线程的状态或行为。
但是经常有一些同时运行的线程需要共享数据,此时就需考虑其他线程的状态和行为,否则就不能保证程序的运行结果的正确性。
Ø共享数据的线程“互斥“锁定
1)线程间的数据共享
为解决操作的不完整性问题,在Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
每个对象都对应于一个可称为"互斥锁"的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
关键字synchronized来与对象的互斥锁联系。
当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。
例7.5银行帐户的存取款线程设计。
本例设计三个类,银行帐户类Account1、存款线程类Save1和取款线程类Fetch1。
程序如下:
classAccount1//帐户缓冲区
{
privateStringname;
privateintvalue;
voidput(inti)//欲存入金额i
{
value=value+i;//存入时,value值增加
}
intget(inti)//欲取金额i,返回实际取到金额
{
if(value>i)
value=value-i;//取走时,value值减少
else//帐户金额不够所取时
{
i=value;
value=0;//取走全部所余金额
}
returni;
}
inthowmatch()//查看帐户上现有金额
{
returnvalue;
}
}
classSave1extendsThread//存款线程
{
privateAccount1a1;
privateintamount;
publicSave1(Account1a1,intamount)
{
this.a1=a1;
this.amount=amount;
}
publicvoidrun()
{
intk=a1.howmatch();
try
{
sleep
(1);//花费时间
}
catch(InterruptedExceptione)
{
System.out.println(e);
}
a1.put(amount);
System.out.println("现有"+k+",存入"+amount+
",余额"+a1.howmatch());
}
publicstaticvoidmain(Stringargs[])
{
Account1a1=newAccount1();
(newSave1(a1,100)).start();
(newSave1(a1,200)).start();
(newFetch1(a1,500)).start();
}
}
classFetch1extendsThread//取款线程
{
privateAccount1a1;
privateintamount;
publicFetch1(Account1a1,intamount)
{
this.a1=a1;
this.amount=amount;
}
publicvoidrun()
{
intk=a1.howmatch();
try
{
sleep
(1);//花费时间
}
catch(InterruptedExceptione)
{
System.out.println(e);
}
System.out.println("现有"+k+",取走"+a1.get(amount)+
",余额"+a1.howmatch());
}
}
程序运行结果:
现有0,存入100,余额100
现有0,存入200,余额300
现有0,取走300,余额0
2)Synchronized
Synchronized锁定一段代码,称为创建一个代码临界区,使得线程必须等候特定资源的所有权。
当第一个线程执行这段代码时,它获取特定的对象的所有权,即拥有该对象的锁。
此时,如果有第二个线程对同一个对象也要执行这段代码时,它试图获取该对象的所有权,但因该对象已被锁定,则第二个线程必须等待,直到锁被释放为止。
第一个线程执行完<语句>后,自动释放了锁,接下去第二个线程获得锁并可运行。
这样就形成了多个线程对同一个对象的“互斥”使用方式,该对象称为“同步对象”。
这种锁定方式是针对某个特定对象而言的。
如果有两个线程同时对两个不同的对象进行操作,则没有锁定,它们可以同时进入代码的临界区