《java网络编程第1章课件》由会员分享,可在线阅读,更多相关《java网络编程第1章课件(28页珍藏版)》请在金锄头文库上搜索。
1、第一章,线程与线程实现,课程目标,线程的概述: 线程的概念 线程的生命周期 线程同步/死锁 线程优先级 线程调度: Thread类和Runnable接口的作用 线程的创建 线程的使用 窗体动画的实现,体验项目,本游戏属于射击游戏,友机和敌机相对飞行;可通过键盘控制友机左右移动,并且发射子弹攻打敌机,当发出的子弹打中敌机后,敌机爆炸,则本游戏结束;当友机和敌机相撞时,两架飞机同时都爆炸,本游戏结束。 游戏运行效果如图所示,该图显示友机发射子弹攻打敌机的效果:,当友机打中敌机时,敌机爆炸,效果如图所示:,线程的概述,线程概念和原理 线程是现代操作系统所提出的一个新概念,线程也被称作“轻量进程”,是
2、比传统进程更小的一.种可以并发执行的执行单位。线程概念的引入提高了程序执行的并发度,从而提高了系统效率。JAVA提供了完全意义的多线程支持。 运用线程的结果类似于大家常说的“并行处理”,其实在我们日常生活中也常常进行着“并行处理”,您的血液循环、饥饿、愤怒可能同时发生;您的视、听、言、行也可能同时进行;您一面写文章、一面打印成报表、一面还收电子邮件,也在同时进行着;您一面开车、一面吹冷气、一面听音乐等都是“并行处理”的例子,JAVA程序语言当然也有这种能耐,即提供并行处理的机制。,JAVA的多线程支持体现在两个方面,第一,JAVA环境本身就是多线程的,它可以利用系统的空闲时间来执行诸如必要的垃
3、圾清除和一般性的系统维护等操作;第二,JAVA还提供了对多线程的语言支持,利用多线程编程接口,编程人员可以很方便地写出支持多线程的应用程序,提高程序的执行效率。 必须注意的是,从JAVA语言规范可以看出,JAVA的多线程支持在某些环境中可能会受平台的限制,并且依赖于某些与平台相关的特性。比如说,如果操作系统本身不支持多线程,JAVA多线程就可能只是“受限”的或是不完全的多线程。,线程的生命周期内的状态,线程的生命周期是指线程从产生到结束的整个过程,在这个过程中线程通常会呈现以下的几种状态:,新线程状态(New Thread),可运行状态(Runnable),运行状态(Running),非运行状
4、态(Not Runnable),死亡状态(Dead),线程的相关概念,线程调度,线程休眠,线程的同步和死锁,线程安全,线程优先级,线程调度,JAVA提供一个线程调度器来监控程序启动后进入可运行状态的所有线程。线程调度器按照线程的优先级决定应调度哪些线程来执行。 线程调度器按线程的优先级高低,选择高优先级线程(进入运行状态)执行,同时线程调度又是抢先式调度,即如果在当前线程执行过程中,一个更高优先级的线程进入可运行状态,则这个拥有更高优先级的线程就立即被调度执行。 抢先式调度又分为:时间片方式和独占方式。 在时间片方式下,当前活动线程执行完当前时间片后,如果有其他处于就绪状态的相同优先级的线程,
5、系统会将线程执行权交给其他可运行状态的同优先级的线程;当前活动线程就会转入等待执行队列中,等待下一个时间片的调度。 在独占方式下,当前活动线程一旦获得线程执行权,将一直执行下去,直到执行完毕或由于某种原因主动放弃CPU执行时间。否则不会让别的线程执行。,在下面几种情况下,当前活动线程会放弃正在占用的CPU: 线程被调用了yield()或sleep()方法主动放弃; 由于当前线程正在进行I/O访问、外存读写、等待用户输入等操作,并因此导致了线程阻塞;或者是在等候一个条件变量,或者线程被调用了wait()方法等情况; 在独占方式下,高优先级的线程执行完毕;在时间片方式下,线程的当前时间片用完,由同
6、优先级的线程参与调度。,线程休眠,Thread类中包含了一个sleep(int)方法,它使当前线程进入等待状态,直到过了指定的时间后,或者是另一个线程对当前线程的Thread对象调用了interrupt()方法,从而中断了该线程。但是过了指定时间后,线程又将变成可运行的,并且会回到调度程序的可运行线程队列中。 线程被调用了yield()或sleep()方法后都会主动放弃正在占用的CPU,但是它们的执行过程会有如下的不同: 如果线程是由于调用了interrupt()方法而中断的,那么休眠的线程会抛出InterruptedException异常,这样线程就知道它是由于调用interrupt()方法
7、而中断的,就不必查看计时器是否过期。然而,yield()方法和sleep() 方法都放弃正在占用的CPU,但yield()方法并不引起休眠,它只是暂停当前线程,这样其它线程就可以运行了。,线程的同步和死锁,当两个或多个线程需要访问同一共享资源时,为了保证数据的准确性,我们需要通过某种方式来确保资源在某一时刻只被一个线程使用,这种方式就是线程同步。 同步通常是通过监控器(Monitor)来实现,一个监控器是作为互斥排它锁(mutex)使用的一个对象,在给定时刻,只能有一个线程拥有该监控器。当一个线程获得了锁,也就进入监控器,所有试图进入已被锁定的监控器的线程都将被暂停,直到拥有监控器的线程退出监
8、控器。如果需要的话,一个拥有监控器的线程可以重用监控器。在JAVA中我们可以通过两种方式来实现同步,这两种方式都使用synchronized关键词,线程同步将在下章介绍。,如果两个线程共享着同一份数据资源,那么需要一些方法以确保它们读写共享数据的正确性,比如,我们必须阻止一个线程在另一个线程读数据的过程中向共享资源里面写数据,因为这样就会导致读出的数据不正确。 线程同步虽然可以保证数据的正确性,但是也会引发一些问题,比如,在线程同步中,资源占用是互斥的,即当某个线程A占用一个共享资源后,又提出了占用其他共享资源的申请,如果此时线程A申请的资源被另一线程B占用,且线程B正在请求其他线程占用的共享
9、资源,这有可能导致这些线程永远请求不到必需的资源而无法继续运行,这就产生了一种特殊现象-死锁。例如,如果线程A锁住了数据1并等待数据2,而线程B锁住了数据2并等待数据1,这样两个线程就发生了死锁现象。,线程安全,如果某段代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其它变量的值也和预期的是一样的,我们就说线程是安全的。 比如一个ArrayList类,在添加一个元素的时候,它可能会分两步来完成:(1)在ItemsSize的位置存放此元素;(2)增大Size的值。 在单线程运行的情况下,如果Size = 0,添加一个元素后,
10、此元素在位置0,而且Size=1;而如果是在多线程情况下,比如有两个线程,线程A先将元素存放在位置0。但是此时CPU调度线程A暂停,线程B得到运行的机会。线程B也向此ArrayList添加元素,因为此时Size仍然等于0(注意,我们假设的是添加一个元素是需要两个步骤,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加Size的值。现在我们来看看ArrayList的情况,元素实际上只有一个,存放在位置0,而Size却等于2。这就是“线程不安全”了。,线程优先级,线程调度器使用线程的优先级来决定线程什么时允许运行。从理论上来说,高优先级的线程将比低优先
11、级的线程得到的CPU时间多,实际上,一个线程获得CPU时间的数量除了依赖于优先级外,还依赖于其他几个因素,例如:操作系统怎样实现多任务将影响线程获得的CPU时间,而且高优先级的线程会抢占低优先级线程的执行权。例如:当低优先级线程正在运行时,一个高优先级的线程从睡眠或等待I/O的状态被重新激活,它将抢占低优先级线程的执行权。线程优先级具体应用将在下章讲解。,线程调度,Thread类和Runnable接口的介绍 在JAVA中线程有两种方法来实现: 第一种:继承Thread类 第二种:实现Runnable接口 Thread类是线程类,它本身实现了Runnable接口,该类封装了线程的方法。要创建一个
12、线程,可以继承Thread类,并通过覆盖Thread类的run()方法来完成实际的工作,用户并不需要直接调用run()方法;而是通过调用Thread类的start()方法,然后该方法会调用run()方法。 实现Runnable接口也可以实现线程的构造,此接口只有一个方法即run()方法,run()方法必须由实现了Runnable接口的类实现。Runnable接口抽象了可执行的代码单元,可以通过任何实现Runnable接口的对象来构造线程。实现Runnable接口时需要实现一个run()方法,它的声明如下: public void run();,在创建了一个实现Runnable接口的类以后,实例
13、化Thread类的对象时可以把实现了线程接口的类作为参数传入,如下所示: new Thread(Runnable threadOb); 在构造函数中,threadOb是实现Runnable接口的一个实例,它定义了线程的执行从哪里开始。写法如:new Thread(Runnable)新线程创建以后,直到调用了它的start()方法后才执行(这个方法在Thread类里声明)。本质上,start()方法执行对run()方法的调用。start()方法如下所示: void start(),实现Runnable接口的例子,public class DoSomething implements Runnab
14、le public void run() /代码 public class Main /Main类为启动线程类: public static void main(String args ) DoSomething ds=new DoSomething();/此类实现了Runnable接口 new Thread(ds).start();/把实现了Runnable接口的类当参数传入Thread类中并启动 ,继承Thread类的例子,public class DoAnotherThing extends Thread public void run() /代码 public class Main /
15、Main类为启动线程类: public static void main(String args ) new DoAnotherThing().start(); /此类已经继承的Thread类所以直接启动就可以了 这两种方法的区别是,如果你的类已经继承了其它的类,那么你只能选择实现Runnable接口了,因为JAVA只允许单继承。,线程的常用方法,用Thread类实现线程调度,通常我们把线程要执行的代码写在run()方法里,如上所说多个线程的run()方法应该交替执行,下面以一个实例来说明它的交替执行。 下面是一个用到了Thread类的示例,一个线程打印0-100,另一个线程打印900-100
16、0,看看会出现什么效果。 public class ThreadApp public static void main(String args ) new ThreadA().start(); /实例化第一个线程并启动 new ThreadB().start(); /实例代第二个线程并启动 ,class ThreadA extends Thread/第一个线程打印0-100 public void run() for(int i=0;i=100;i+) System.out.print(i+ ); class ThreadB extends Thread/第二个线程打印900-1000 public void run() for(int i=900;i=1000;i+) System.out.print(i+ ); ,可以看出,图1-3的输出结果不是按顺序先打印1-100,然后打印900-1000,而是交叉打印的。这说明线程是分时间片运行的,当第一个线程运行完自己的时间片后,不管他是否运行完,都要暂时等待,之后运行第二个线