您好,欢迎访问三七文档
线程目标•什么是线程•创建线程的两种方法•线程的构造•线程的调度与控制•多线程的互斥和同步•总结线程的概念•多任务:计算机在看上去几乎同一时间内运行多个程序。•多线程:单个程序内部也可以在同一时间进行多种运算。•一个线程是一个程序内部的顺序控制流。–不是程序,自己本身不能运行,必须在程序中运行–如何在一个程序内部实现多个线程。线程与进程的区别•每个进程都有独立的代码和数据空间,进程切换的开销大。•线程:轻量的进程,同一类线程共享代码和数据空间,线程切换的开销小。•多进程:在操作系统中,能同时运行多个任务(程序)。•多线程:在同一应用程序中,有多个顺序流同时执行。说明:线程是程序内的顺序控制流,只能使用分配给程序的资源和环境。线程的概念模型创建线程在Java中通过run方法为线程指明要完成的任务,有两种技术来为线程提供run方法。1.继承Thread类并重载run方法。•Thread类:是专门用来创建线程和对线程进行操作的类。Thread中定义了许多方法对线程进行操作。•Thread类在缺省情况下run方法是空的。可以通过继承Thread类并重写Thread类的run方法实现用户线程。2.执行Runnable接口的类实现run方法。通过建立一个实现了Runnable接口的对象,并以它作为线程的目标对象来创建一个线程。两种方法的比较1.两种方法均需执行线程的start方法为线程分配必须的系统资源、调度线程运行并执行线程的run方法。2.在具体应用中,采用哪种方法来构造线程体要视情况而定。通常,当一个线程已继承了另一个类时,就应该用第二种方法来构造,即实现Runnable接口。线程的调度•线程的状态1.创建状态•当用new操作符创建一个新的线程对象时,该线程处于创建状态。•处于创建状态的线程只是一个空的线程对象,系统不为它分配资源•此时只能调用start方法启动该线程,调用其它任何方法都会产生线程非法状态异常。2.可运行状态•执行线程的start()方法将为线程分配必须的系统资源,安排其运行,并调用线程体—run()方法,这样就使得该线程处于可运行(Runnable)状态。•这一状态并不是运行中状态(Running),因为线程也许实际上并未真正运行。3.不可运行状态当发生下列事件时,处于运行状态的线程会转入到不可运行状态。•调用了sleep()方法;•线程调用wait方法所等待的特定条件的满足•线程输入/输出阻塞返回可运行状态:•处于睡眠状态的线程在指定的时间过去后•如果线程在等待某一条件,另一个对象必须通过notify()或notifyAll()方法通知等待进程条件的改变•如果线程是因为输入/输出阻塞,输入/输出完成4.消亡状态•当线程的run方法执行结束后,该线程自然消亡。线程的优先级•线程的优先级用数字来表示,范围从1到10,即Thread.MIN_PRIORITY到Thread.MAX_PRIORITY。一个线程的缺省优先级是5,即Thread.NORM_PRIORITY。•intgetPriority();•voidsetPriority(intnewPriority);•注意:并不是在所有系统中运行Java程序时都采用时间片策略调度线程,所以一个线程在空闲时应该主动放弃CPU,以使其他同优先级和低优先级的线程得到执行。线程的同步•问题的提出临界资源问题classstack{intidx=0;char[]data=newchar[6];publicvoidpush(charc){data[idx]=c;idx++;}publiccharpop(){idx--;returndata[idx];}}两个线程A和B在同时使用Stack的同一个实例对象,A正在往堆栈里push一个数据,B则要从堆栈中pop一个数据。1)操作之前data=|p|q|||||idx=22)A执行push中的第一个语句,将r推入堆栈;data=|p|q|r||||idx=2A还未执行idx++语句,A的执行被B中断,B执行pop方法,返回q:data=|p|q|r||||idx=14〕A继续执行push的第二个语句:data=|p|q|r||,||idx=2最后的结果相当于r没有入栈。•注意:产生这种问题的原因在于对共享数据访问的操作的不完整性。生产者-消费者问题classProducerimplementsRunnable{SyncStacktheStack;publicProducer(SyncStacks){theStack=s;}资源SyncStack生产者消费者publicvoidrun(){charc;for(inti=0;i20;i++){c=(char)(Math.random()*26+'A');theStack.push(c);System.out.println(Produced:+c);try{Thread.sleep((int)(Math.random()*100));}catch(InterruptedExceptione){}}}}classConsumerimplementsRunnable{SyncStacktheStack;publicConsumer(SyncStacks){theStack=s;}publicvoidrun(){charc;for(inti=0;i20;i++){c=theStack.pop();System.out.println(Consumed:+c);try{Thread.sleep((int)(Math.random()*1000));}catch(InterruptedExceptione){}}}}publicclassSyncTest{publicstaticvoidmain(Stringargs[]){SyncStackstack=newSyncStack();Runnablesource=newProducer(stack);Runnablesink=newConsumer(stack);Threadt1=newThread(source);Threadt2=newThread(sink);t1.start();t2.start();}}怎样实现同步对于访问某个关键共享资源的所有方法,都必须把它们设为synchronized例如:synchronizedvoidf(){/*...*/}synchronizedvoidg(){/*...*/}如果想保护某些资源不被多个线程同时访问,可以强制通过synchronized方法访问那些资源。调用synchronized方法时,对象就会被锁定。说明:•当synchronized方法执行完或发生异常时,会自动释放锁。•被synchronized保护的数据应该是私有(private)的。线程间的相互作用:(1)wait,nofity,notifyAll必须在已经持有锁的情况下执行,所以它们只能出现在synchronized作用的范围内.这些方法都是在java.lang.Object中定义的。(2)wati的作用:释放已持有的锁,进入wait队列.(3)notify的作用:唤醒wait队列中的一个线程并把它移入锁申请队列.(4)notifyAll的作用:唤醒wait队列中的所有的线程并把它们移入锁申请队列.总结•何时使用多线程技术,以及何时避免用它,这是我们需要掌握的重要课题。它的主要目的是对大量任务进行有序的管理。通过多个任务的混合使用,可以更有效地利用计算机资源,或者对用户来说显得更方便。资源均衡的经典问题是在IO等候期间如何利用CPU。多线程的主要缺点包括:(1)等候使用共享资源时造成程序的运行速度变慢。(2)对线程进行管理要求的额外CPU开销。(3)复杂程度无意义的加大,比如用独立的线程来更新数组内每个元素的愚蠢主意。(4)漫长的等待、浪费精力的资源竞争以及死锁等多线程症状。•除此以外,运用线程时还要注意一个非常特殊的问题。由于根据Java的设计,它允许我们根据需要创建任意数量的线程――至少理论上如此(例如,假设为一项工程方面的有限元素分析创建数以百万的线程,这对Java来说并非实际)。然而,我们一般都要控制自己创建的线程数量的上限。因为在某些情况下,大量线程会将场面变得一团糟,所以工作都会几乎陷于停顿。临界点并不象对象那样可以达到几千个,而是在100以下。一般情况下,我们只创建少数几个关键线程,用它们解决某个特定的问题。这时数量的限制问题不大。但在较常规的一些设计中,这一限制确实会使我们感到束手束脚。•大家要注意线程处理中一个不是十分直观的问题。由于采用了线程“调度”机制,所以通过在run()的主循环中插入对sleep()的调用,一般都可以使自己的程序运行得更快一些。作业•1.用继承Thread类和执行Runnable接口的方法创建两个线程,并测试这两个线程的同时运行情况。a.将两个线程设为同优先级,比较运行情况。b.将两个线程设为不同优先级,比较以上两种情况。•2.编写两个线程:一个线程向数组中存数据,一个线程向数组中取数据。•3.创建两个线程的实例,分别将一个数组从小到大和从大到小排列.输出结果.
本文标题:线程
链接地址:https://www.777doc.com/doc-3896464 .html