10线程知识点.docx

上传人:b****7 文档编号:10789150 上传时间:2023-02-22 格式:DOCX 页数:29 大小:162.89KB
下载 相关 举报
10线程知识点.docx_第1页
第1页 / 共29页
10线程知识点.docx_第2页
第2页 / 共29页
10线程知识点.docx_第3页
第3页 / 共29页
10线程知识点.docx_第4页
第4页 / 共29页
10线程知识点.docx_第5页
第5页 / 共29页
点击查看更多>>
下载资源
资源描述

10线程知识点.docx

《10线程知识点.docx》由会员分享,可在线阅读,更多相关《10线程知识点.docx(29页珍藏版)》请在冰豆网上搜索。

10线程知识点.docx

10线程知识点

线程是JAVA的高级部分,我们的重点是理解JAVA中线程的概念,原理为今后的的学习高级部分,以及面试做准备。

然后是多线程的实现方式以及简单的启动,停止线程方法,为游戏做准备。

多任务处理:

什么是多任务呢?

多任务就是几个任务同时运行。

在我们上网的时候,即可以聊天,也可以听音乐,也可以浏览网页。

这就是多任务。

它有个特点:

就是在一个任务还没有做完的的时候,又去做另外的任务。

在程序里的多任务处理是怎么样的呢?

它也是指在同一个时候执行多个任务,它不必等到某个任务执行完之后,再去做其他的任务。

CPU在处理的时候,先是执行一个任务的一段代码,然后接着执行另一个任务的一段代码。

交替执行直到任务结束。

现实生活中这样的例子是很多的。

如:

有个柜台只有一个营业员。

这时候来了二个顾客。

那么这个营业员应该怎么样去应对呢?

她问第一个顾客要买什么商品,然后把商品给第一个顾客看;在第一个顾客看的时候,又去问第二个顾客要买什么商品,然后把商品给第二个顾客看。

在第二个顾客看时候,又去问第一个顾客对商品满不满意。

如果顾客说再看看,那么营业员又去问第二个顾客满不满意。

如果第二个顾客说满意,营业员就叫顾客付款,然后结束第二个顾客的营业任务,然后再去问第一个顾客满不满意。

如果第一个顾客也满意付款。

那么整个任务就结束了。

 

多任务处理的2种类型:

◆多任务处理有两种类型:

-基于进程

-基于线程

◆进程是指一种“自包容”的运行程序,有自己的地址空间;

当我们打开windows任务管理器的时候,就有一个进程的选项卡。

在这个选项卡里,我们可以发现有很多当前计算机正在运行的应用程序。

这些正在运行的每一个应用程序都叫一个进程。

它们都会占用一定的内存,都有自己的地址空间。

◆基于进程的特点是允许计算机同时运行两个或更多的程序。

在JAVA中是可以启动一个进程的。

就是说通过JAVA代码,我们可以打开另一个应用程序

代码示例:

//创建一个操作系统进程,用指定的的应用程序去打开指定的文件

ProcessBuilderprocess=newProcessBuilder(

"C:

/WINDOWS/NOTEPAD.EXE",//应用程序地址

"c:

/2.txt");//文件地址

try{

process.start();//启动进程

}catch(IOExceptione){

e.printStackTrace();

}

在上面的示例中,我们是开启了一个进程,用指定地址的应用程序(这里是记事本),去打开指定的文件(这里是C盘下的2.txt)

线程

和多进程不同,什么是多线程呢?

线程是进程内部单一的一个顺序控制流。

所以一个进程包含很多线程。

基于线程的多任务处理环境中,线程是最小的处理单位。

我们在使用word文档时,打开一个文件菜单,这就是开启了一线程,把一行字选中,加粗,这也是一个线程。

线程的概念其实很简单,线程就是程序运行时的路径,它决定将要执行什么代码。

所以当我们运行main方法时,其实就已经开启了一个线程了。

基于线程的多任务处理的优点

◆基于线程所需的开销更少

–在多任务中,各个进程需要分配它们自己独立的地址空间

–多个线程可共享相同的地址空间并且共同分享同一个进程

网站是个很典型的多任务处理的例子,当不同的用户同时去访问一个网站时,网站就需要进行多任务处理。

以前使用CGI(通用网关接口技术),CGI是一个多进程的技术,当一个用户去访问网站时,是开启一个进程,这样的话,当有多个用户同时访问时,那么就得开启多个进程。

每一个进程都有自己独立的地址空间,所以这样的网站内存很快就消耗掉了。

不但可连接的用户少,而且很容易受到恶意攻击。

在JAVA里,采用的servlet技术,是一个基于多线程的技术,共享同一个进程,共享相同的地址空间,这样的话,当有多个用户同时访问时,性能不会下降多少。

◆进程间调用涉及的开销比线程间通信多

◆线程间的切换成本比进程间切换成本低

 

那么什么又是单线程,什么又是多线程呢?

代码示例:

publicstaticvoidmain(String[]args){

a();

b();

c();

}

代码执行的顺序图如下:

 

在上面的例子里,大家可以看出,在main方法里执行时,必须要等到a()方法执行完了之后,才能执行b()方法。

b()方法执行完以后才能执行C()方法。

这样在等到一个方法执行完以后再执行另一个方法的模式叫单线程。

我以前用到的程序大部分都是单线程的。

那么多线程是怎么一回事呢?

 

 

在上面的例子,可以看出,多线程是多个方法同时执行,在a()方法执行时,同时执行b()方法,换句话说,b()方法不必等到a()方法执行完以后再去执行。

这就是多线程。

就是说在一个程序中,同时有多个执行路径存在。

《西游记》里孙语空去打妖精,这时候他一个人去打三个妖精,它要把一个妖精打死之后,才能去打第二个妖精,第二个妖精打死之后,再打第三个妖精。

这时候它是单线程的。

一个打妖精很累,它就拔三根毫毛,变成三个小孙悟空,一个小孙悟空打一个妖精,就是说它不必等到第一个妖精打死之后再去打第二个妖精,而是同时去打。

这就叫多线程。

◆在Java程序启动时,一个线程立刻运行,该线程通常称为程序的主线程。

也就是main方法运行时就产生一个主线程。

◆主线程的特点:

–最先开始

–最后结束

–由他产生其他子线程

–通常它必须最后完成执行,因为它执行各种关闭动作。

 

这在上例中,孙悟空就好比是主线程,它变出的三个小猴子是子线程。

小猴子是由孙悟空这个主线程产生的,小猴子打死妖精之后,由孙悟空再把小猴子回收成毫毛。

代码示例:

publicstaticvoidmain(String[]args){

//获得当前线程对象,即主线程

Threadt=Thread.currentThread();

System.out.println("当前线程是:

"+t);

//输出结果:

"当前线程是:

Thread[main,5,main]"

/*在中括号中第一个main指的是线程的名字,

*5指的是线程的优先级,

*第二个main是线程所在的线程组

*/

t.setName("主线程");//设置线程名字

System.out.println("当前线程名是:

"+t);

//输出结果是:

"当前线程名是:

Thread[主线程,5,main]"

try{

for(inti=0;i<3;i++){

System.out.println(i);

/*

*线程休眠1500毫秒

*就是线程暂时停止运行1500毫秒

*

*sleep是线程类Thread的静态方法,

*指的是当前线程休眠1500毫秒(这里是主线程)

*/

Thread.sleep(1500);

}

}

catch(InterruptedExceptione){

System.out.println("主线程被中断");}

}

在这个例子中,是单线程的程序,而main方法所在的线程就是主线程。

那么如何去通过这个主线程去启动一个子线程呢?

◆通过以下两种方法创建Thread对象:

1、-声明一个Thread类的子类,并覆盖run()方法。

publicclassThreads{

publicstaticvoidmain(String[]args){

Oneone=newOne();//创建线程对象

one.start();//启动线程

for(inti=65;i<75;i++){//打印字母

System.out.println((char)i);

}

}

}

classOneextendsThread{//继承Thread线程对象

publicvoidrun(){//启动线程后自动调用子类重写父类的run()方法

for(inti=0;i<10;i++){

System.out.println(i);

}

}

}

在上面的例子中定义了一个线程,并且重写了父类的run()方法。

然后在main方法这个主线程中启动这个线程,启动一个线程使用线程对象的start()方法。

这时候就用自动的调用父类的run()方法,循环打出一串数字。

在main主线程里也有一个循环,是打出字母。

因为是两个线程同时候运行,这时候打印的结果是数字和字线交替打印。

说明主线程不必等到子线程结束以后,就继续执行下面的语句。

有些同学可能会说了,既然启动线程是调用run()方法。

那么我直接调用run()方法不就行了,为什么要调用start()方法呢?

上面例子中,main()方法改为

Oneone=newOne();//创建线程对象

one.run();//启动线程

for(inti=65;i<75;i++){//打印字母

System.out.println((char)i);

}

不也一样吗?

这样的想法是错误的。

因为启动线程是使用start()方法。

这时候在主线程之外另外开启了一个线程,就是说主线程不必等到子线程结束以后再进行其他的动作。

所以这时候字母和数字循环打印。

而如果直接调用run()方法,其实只是普通方法的调用,并没有开启一个线程,所以是单线程的程序。

上面例子中,就不会是数字和字母交替打印,而是先把数字打印完毕,再打印字母。

2、声明一个实现Runnable接口的类,并实现run()方法。

publicclassThreads{

publicstaticvoidmain(String[]args){

Threadtwo=newThread(newTwo());//创建线程对象,而其中的参数是实现了Runnable接口的类对象。

two.start();//启动线程

}

}

classTwoimplementsRunnable{//实现Runnable接口,并实现这个接口定义的run()方法

publicvoidrun(){

System.out.println("线程启动");

}

}

上面的例子是由一个类去实现Runnable接口,然后实现Runnable里定义的run()方法。

然后再定义一个Thread线程类对象,将这个实现了Runnable接口的类对象作为参数传过去。

这样的话,就是把线程对象和这个实现了Runnable接口的类对象作了一个关联,在启动线程的时候,就是去执行实现了Runnable接口的类对象里重写的run()方法。

 

上面两种方法都可以实现线程的启动,但通常采用第二种方法启动线程,原因是如果一个类继承了另一个类的时候,那么就不能再继承其他类了,但接口可以多实现,所以实现接口的方式更加灵活一些。

线程启动之后,会有不同的状态,它有什么样的状态呢?

 

从上面的图可以看出线程经常处于暂时停止执行的状态,那么什么时候线程暂时停止执行呢?

◆线程:

-线程优先级比较低,因此它不能获得CPU时间。

-使用sleep()方法使线程睡眠。

-通过调用wait()方法,使线程等待。

-通过调用yield()方法,线程已显式出让CPU控制权。

(挂起)

-线程由于等待一个文件I/O事件被阻塞。

线程优先级

线程是有优先级的,就是说当两个线程同时启动或同时去访问一个对象时,优先级高的线程会拥有更高的访问权。

线程的优先级有1—10级,数字越大,优先级越高。

◆Java中的线程优先级是在Thread类中定义的常量

-NORM_PRIORITY:

值为5

-MAX_PRIORITY:

值为10

-MIN_PRIORITY:

值为1

◆缺省优先级为NORM_PRIORITY5级

◆有关优先级的方法有两个:

-finalvoidsetPriority(intnewp):

修改线程的当前优先级

-finalintgetPriority():

返回线程的优先级

 

代码示例:

publicclassThreads{

publicstaticvoidmain(String[]args){

Oneone=newOne();

Threadtwo=newThread(newTwo());

one.setPriority

(1);//设置第一个线程优先级为1级

two.setPriority(10);//设置第二个线程优先级为10级

one.start();//启动第一个线程

two.start();//启动第二个线程

}

}

classOneextendsThread{

publicvoidrun(){

System.out.println("第一个线程");

}

}

classTwoimplementsRunnable{

publicvoidrun(){

System.out.println("第二个线程");

}

}

上面例子运行的结果是“第二个线程第一个线程”。

虽然第一个线程比第二个线程先调用start()方法,但由于第一个线程的线程优先级比第二个线程低,所以优先启动第二个线程。

 

线程同步

《西游记》里孙悟空拔出两根毫毛,变出两个小猴子,一个小猴子去把妖精往死里打,而另一个小猴子去问妖精话。

这样的话就有问题了,如果第一个小猴子把妖精打死了,那么第二个猴子就没得问了。

在网站访问的时候,如果两个用户同时去访问同一个数据库的同一张表,如果一个用户去查询,而另一个用户去修改。

那么第一个用户访问到的数据是修改之前的数据,还是修改之后的数据呢?

所以,为了解决上面的问题,线程采用了同步的概念。

为了确保在任何时间点一个共享的资源只被一个线程使用,使用了“同步”。

就是说当两个线程同时访问同一个对象的时候,只允许一个线程访问。

所以使用同步可以保证任何时候只有一个线程访问一个方法或对象

◆线程同步,使用同步关键字synchronized来进行标识

代码示例:

需求:

模拟银行取款的业务。

有两个顾客去取钱的业务。

 

publicclassBankTest{

publicstaticvoidmain(String[]args){

Bankbank=newBank();//产生银行对象

Manman1=newMan(bank,1500);//产生第一个顾客对象

Manman2=newMan(bank,1300);//产生第二个顾客对象

try{

Thread.sleep(3000);//主线程休眠3000毫秒

}catch(InterruptedExceptione){

e.printStackTrace();

}

System.out.println("银行最后余额"+bank.getMoney());//获得当前银行帐户最后余额

}

}

/**

*银行帐户类

*

*/

classBank{

/**

*当前帐户余额

*/

privateintmoney=2000;

/**

*返回当前金额

*/

publicintgetMoney(){

returnmoney;

}

/**

*设置当前金额

*@parampressMoney

*/

publicvoidsetMoney(intpressMoney){

if(pressMoney<=money){//当取走的钱小于当前金额时候,允许操作

try{

Thread.sleep(1000);//线程休眠1000毫秒

}catch(InterruptedExceptione){

//TODO自动生成catch块

e.printStackTrace();

}

money-=pressMoney;//存款减少

System.out.println("取走"+pressMoney);

}

else{//当取的钱大于帐户余额,拒绝操作

System.out.println("帐户已超支,操作失败");

}

System.out.println("最后"+money);//打印最后金额

}

}

/**

*顾客类

*@authorAdministrator

*

*/

classManextendsThread{

Bankbank;//银行帐户

intmoney;//顾客要取金额

Man1(Bankbank,intmoney){

this.bank=bank;

this.money=money;

this.start();

}

//调用帐户取钱方法

publicvoidputMoney(){

bank.setMoney(money);

}

/**

*线程run方法

*/

publicvoidrun(){

putMoney();

}

}

以上代码运行结果:

取走1300

取走1500

最后-800

最后-800

银行最后余额-800

上面例子中,对银行而言,是不允许帐户余额为负数的,所以在帐户类的setMoney()方法中,先判断取的钱是否大于帐户余额,如果大于帐户余额则操作失败。

但这时候,有两个顾客(Man类对象)线程同时对同一个帐户进行操作。

因为它们取的钱都小于帐户余额,这样的话,两个顾客都允许对帐户操作,这样帐户余额都会减少,因为两个顾客取钱的数目总和大于帐户余额,所以最后的结果帐户余额就是负数。

那么怎么防止这种情况呢?

解决办法就是在setMoney()方法前面加上一个同步的关键字synchronized。

使当前帐户类对象同步,这样在一线程在访问帐户时,另一个线程就不能再对帐户进行操作了。

上面代码改为:

/**

*设置当前金额

*@parampressMoney

*/

publicsynchronizedvoidsetMoney(intpressMoney){

…………

}

再运行时,结果就变成:

取走1500

最后500

帐户已超支,操作失败

最后500

银行最后余额500

 

上面的方法叫同步方法,使用同步方法的时候,锁住的是本身这个类对象,所以一旦这个类对象被一个线程使用,那个这个类对象就被锁住,那么其他的同步方法也不能被其他线程所访问了。

和同步方法对应的,还有同步块。

就是在一个方法里不要求全部同步,而只是在调用对象时,对这个对象同步,(锁住的是这个对象)这时候就得用同步块。

同步块写在方法里。

 

代码示例:

classOne{

voiddisplay(intnum){

System.out.print(""+num);

try{

Thread.sleep(1000);

}catch(InterruptedExceptione){

System.out.println("中断");

}

System.out.println("完成");

}

}

classTwoimplementsRunnable{

intnumber;

Oneone;

Threadt;

publicTwo(Oneone_num,intn){

one=one_num;

number=n;

t=newThread(this);

t.start();

}

publicvoidrun(){

synchronized(one){//同步块,对one这个对象同步,当前线程对one这个对象进行访问时,就将这个对象锁住。

one.display(number);

}

}

}

 

死锁

◆当两个线程循环依赖于一对同步对象时将发生死锁。

例如:

一个线程进入对象ObjA上的监视器,而另一个线程进入对象ObjB上的监视器。

如果ObjA中的线程试图调用ObjB上的任何synchronized方法,就将发生死锁。

◆死锁很少发生,但一旦发生就很难调试

线程之间的通信:

在上面《西游记》里孙悟空拔出两根毫毛,变出两个小猴子,一个小猴子去把妖精往死里打,而另一个小猴子去问妖精话。

这时候就是两个线程去访问同一个对象,如果妖精这个对象加了同步的话,那么当问话的小猴子访问的时候,那么打妖精的小猴子就只有暂时停止运行,进行等待。

等到问话的小猴子问完了以后,就会通知打妖精的小猴子——我问完了,你可以把它打死了,这时候打妖精的小猴子就继续进行打妖精的任务。

象这样两个线程访问同一个对象,当一个线程访问时,其他线程进行等待,当访问线程放弃当前对象使用权时,通知其他线程这样的机制就叫线程之间的通信。

◆Java提供了一个精心设计的线程间通信机制,使用wait()、notify()和notifyAll()方法。

◆这些方法是作为Object类中的final方法实现的。

所以不能被子类重写

◆这三个方法仅在synchronized方法中才能被调用。

 

wait、notify、notifyAll都是Object类定义的方法,不要理解为线程类定义的方法,因为所有的类都是直接或间接继承于Object类的,所以所有的类都会拥有这三个方法。

◆wait()方法告知被调用的线程退出监视器并进入等待状态,直到其他线程进入相同的监视器并调用notify()方法。

◆notify()方法通知同一对象上第一个调用wait()线程。

◆notifyAll()方法通知调用wait()的所有线程,具有最高优先级的线程将先运行。

 

线程间通信最典型的例子就是生产者、消费者和仓库的关系。

代码示例:

publicclassCreates{

publicstaticvoidmain(String[]args){

仓库depot=new仓库();//创建仓库对象

/**

*生产者和消费者共享一个仓库资源

*/

new生产者(depot).start();

new消费者(depot).start();

}

}

class仓库{

booleanisfull;//判断当前仓库是否为空

}

class生产者extendsThread{

仓库depot;

生产者(仓库depot){

this.depot=depot;

}

/**

*生产方法

*/

privatevoidcreate(){

synchronized(

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > PPT模板 > 商务科技

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1