1、第6章 Java多线程机制,6.1 线程概述 6.2 Thread的子类创建线程 6.3 使用Runable接口 6.4 线程的常用方法 6.5 线程同步 6.6 线程之间的协作 6.7 线程联合 6.8 守护线程,6.1 线程概述,程序是一段静态的代码,它是应用软件执行的蓝本。 进程是程序的一次动态执行过程,它对应了程序从加载、执行至执行完毕的一个完整过程,这个过程也是进程本身从产生、发展至消亡的过程。 线程是比进程更小的执行单位,一个进程在其执行过程中,可以产生多个线程,形成多条执行线索,每条线索,即每个线程也有它自身的产生、存在和消亡的过程,也是一个动态的概念。 Java的多线程就是在操
2、作系统每次分时给Java程序一个时间片的CPU时间内,在若干个独立的可控制的线程之间切换。,Java进程中一定有主线程,负责main方法的执行。 在main方法的执行中再创建的线程,就称为程序中的其它线程。 如果main方法中没有创建其他的线程,那么当main方法执行完最后一个语句,JVM就会结束我们的Java应用程序。 如果main方法中又创建了其他线程,那么JVM就要在主线程和其他线程之间轮流切换,保证每个线程都有机会使用CPU资源,JVM一直要等到程序中的所有线程都结束之后,才结束我们的Java应用程序。,6.1.1 Java中的线程,6.1.2 线程的状态与生命周期,新建 当一个Thr
3、ead类或其子类的对象被声明并创建时,新生的线程对象处于新建状态。此时它已经有了相应的内存空间和其它资源。 就绪 线程创建之后就具备了运行的条件,一旦调度机制把CPU时间片分配给线程,线程开始运行了(运行run()方法)。 死亡 run方法结束 。 此时,调度机制将释放掉分配给线程的内存。 阻塞 线程能够运行,但有某个条件阻止它的运行。此时,调度机制将忽略该线程,不会给线程分配CPU时间片。,6.1.3 启动线程,线程创建后仅仅是占有了内存资源,也即现在处于新建状态,在JVM管理的线程中还没有这个线程。 线程必须调用start()方法通知JVM,也即处于就绪状态,这时JVM就会知道又有一个新一
4、个线程排队等候切换了。 线程启动后,由线程执行机制调用run()。,6.1.4 阻塞线程,一个线程进入阻塞状态,可能有如下原因: 调用sleep(int millisecondes)使线程进入休眠状态。 线程要执行一段同步代码,由于无法获得相关的同步锁而陷入阻塞状态,只有等获得了同步锁,才能进入就绪状态。 线程试图在某个对象上调用其同步控制方法,但是对象锁不可用。 通过调用wait()使线程挂起。直到线程得到notify()或notifyAll()消息,线程才会进入就绪状态。 线程在等待某个输入/输出完成。,6.2 Thread的子类创建线程,用Thread子类创建线程对象 。 编写Threa
5、d类的子类时,需要重写父类的run方法,其目的是规定线程的具体操作,否则线程就什么也不做,因为父类的run方法中没有任何操作语句。 当调度机制将CPU时间片分配给线程时,如果线程是Thread的子类创建的,该类中的run方法就立刻执行。,例子1,class Lefthand extends Thread public void run() for(int i=1;i=100;i+) System.out.println(“我是左手线程“); ,class Righthand extends Thread public void run() for(int i=1;i=100;i+) Syste
6、m.out.println(“我是右手线程“); ,public class Example6_1 public static void main(String args) Thread left=new Lefthand() ; /创建线程Thread right=new Righthand();left.start(); right.start();for(int i=1;i=100;i+) System.out.println(“我是主线程“); ,6.3 使用Runnable接口,实现Runnable接口 定义run( )方法 构造线程:Thread(Runnable target);
7、 该构造方法中的参数是一个Runnable类型的接口,因此,在创建线程对象时必须向构造方法的参数传递一个实现Runnable接口类的实例,该实例对象称作所创线程的目标对象。 当线程调用start方法后,一旦轮到它来享用CPU资源,目标对象就会自动调用接口中的run方法(接口回调)。,例子2,class Lefthand implements Runnable public void run() for (int i = 1; i = 109; i+) System.out.println(“我是左手线程“); ,class Righthand implements Runnablepublic
8、 void run() for (int i = 1; i = 109; i+) System.out.println(“我是右手线程“); ,public class Example6_2 public static void main(String args) Thread left = new Thread(new Lefthand();Thread right = new Thread(new Righthand();left.start();right.start();for (int i = 1; i = 100; i+) System.out.println(“我是主线程“);
9、,6.4线程的常用方法,void start() 线程调用该方法将启动线程,使之从新建状态进入就绪队列排队,一旦轮到它来享用CPU资源时,就可以脱离创建它的线程独立开始自己的生命周期了。,void run() Thread类的run()方法与Runnable接口中的run()方法的功能和作用相同,都用来定义线程对象被调度之后所执行的操作,都是系统自动调用而用户程序不得引用的方法。系统的Thread类中,run()方法没有具体内容,所以用户程序需要创建自己的Thread类的子类,并重写run()方法来覆盖原来的run()方法。当run方法执行完毕,线程就变成死亡状态。,6.4线程的常用方法(续)
10、,void sleep(int millsecond) 线程占有CPU期间,执行sleep方法来使自己放弃CPU资源,休眠一段时间。休眠时间的长短由sleep方法的参数决定,millsecond是毫秒为单位的休眠时间。如果线程在休眠时被打断,JVM就抛出InterruptedException异常。因此,必须在try-catch语句块中调用sleep方法。,6.4线程的常用方法(续),boolean isAlive() 测试线程是否已经还存活。如果一个线程已经启动但没有死亡,则返回true,否则返回false。 一个已经运行的线程在没有进入死亡状态时,不要再给线程分配实体,由于线程只能引用最后
11、分配的实体,先前的实体就会成为“垃圾”,并且不会被垃圾收集机收集掉 。,6.4线程的常用方法(续),String getName() 返回该线程的名称。 void setName(String name) 改变线程名称,使之与参数 name 相同 。 static void yield() 暂停当前正在执行的线程对象,并执行其他线程。 void setPriority(int newPriority) 更改线程的优先级。 int getPriority() 返回线程的优先级。,6.4线程的常用方法(续),static Thread currentThread() currentThread()
12、方法是Thread类中的类方法,可以用类名调用,该方法返回当前正在使用CPU资源的线程。,6.4线程的常用方法(续),void interrupt() intertupt方法经常用来“吵醒”休眠的线程。当一些线程调用sleep方法处于休眠状态时,一个占有CPU资源的线程可以让休眠的线程调用interrupt 方法“吵醒”自己。,6.4线程的常用方法(续),public Thread.State getState() 返回该线程的状态。 该方法用于监视系统状态。 NEW,RUNNABLE,BLOCKED,WAITING。,6.4线程的常用方法(续),例子3,本例中,有两个线程:student和t
13、eacher,其中student准备睡一个小时后再开始上课,teacher输出3句”上课”后,吵醒了student。,class A implements Runnable Thread student, teacher;A() teacher = new Thread(this);student = new Thread(this);teacher.setName(“王教授“);student.setName(“张三“);,public void run() if (Thread.currentThread()=student) try System.out.println(student.
14、getName() + “正在睡觉, 不听课“);Thread.sleep(1000 * 60 * 60);catch (InterruptedException e) System.out.println(student.getName() + “被老师叫醒了“);System.out.println(student.getName() + “开始听课“);, else if (Thread.currentThread()=teacher) for (int i = 1; i = 3; i+) System.out.println(“上课!“);try Thread.sleep(500);
15、catch (InterruptedException e) student.interrupt(); / 吵醒student ,public class Example6_3 public static void main(String args) A a = new A();a.student.start();a.teacher.start(); ,6.5线程同步,引入同步的原因线程同步是为了保证在多个线程访问同一个对象的时候不会出现访问冲突的一种机制。同步机制的实现通过给对象(或类)加锁来完成线程的同步。synchronized关键字可以作为方法的修饰符,也可作为方法内的语句,也就是平时
16、说的同步方法和同步语句块。,6.5.1 同步方法,把synchronized当作实例方法修饰符时,示例代码如下: public synchronized void methodAAA()/. 它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的类所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。,6.5.1 同步方法(续),把synchronized当作类方法修饰符时,示例代码如下: class Foo public synchronized static void me
17、thodAAA()/. 它锁定的是调用这个方法的对象所属的类。同属Foo类的对象P1和P2在线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。,class P synchronized void print() for (int i = 1; i = 900; i+)System.out.println(“我是线程“ + i); class Lefthand extends Thread public void run() P p = new P();p.print(); class Righthand extends Thread public void run() P p =
18、new P();p.print(); ,例子4,public class Example6_4 public static void main(String args) Lefthand left;Righthand right;left = new Lefthand(); right = new Righthand();left.start();right.start(); ,6.6 线程之间的协作,等待与通知机制一个线程在使用的同步方法中时,可能根据问题的需要,必须使用wait()方法使本线程等待,暂时让出CPU的使用权,并允许其它线程使用这个同步方法。其它线程如果在使用这个同步方法时如果
19、不需要等待,那么它用完这个同步方法的同时,应当执行notifyAll()方法通知所有的由于使用wait()方法而处于等待的线程结束等待。,例子5,public class Example6_5 implements Runnableprivate int contents;private boolean available = false;private Thread producer;private Thread consumer;public synchronized int get() while (available = false) try wait(); /释放锁,等候produc
20、er放值 catch (InterruptedException e) available = false;System.out.println(“Consumer gets:“ + contents);notifyAll();return contents;,public synchronized void put(int value) while (available = true) try wait(); /释放锁,等候consumer取值 catch (InterruptedException e) contents = value; available = true;System.o
21、ut.println(“Producer produces:“+contents);notifyAll();,public void run()if (Thread.currentThread() = producer)for(int i=0;i=9;i+) put(i);elsefor(int i=0;i=9;i+) get(); public Example6_5()producer = new Thread(this);consumer = new Thread(this);consumer.start();producer.start();public static void main
22、(String args)new Example6_5(); ,线程 consumer,get(),判断当前是否存有值,有,没有,设置为无 取值 唤醒producer,释放锁定 等待新值,线程 producer,put(),判断当前是否存有值,有,没有,释放锁定 等待取值,放值 设置为有 唤醒consumer,6.7 线程联合,一个线程A在占有CUP资源期间,可以让其它线程调用join()和本线程联合。 final void join() throws InterruptedException 如果线程A在占有CUP资源期间一旦联合B线程,那么A线程将立刻中断执行,一直等到它联合的线程B执行完
23、毕,A线程再重新排队等待CPU资源,以便恢复执行。,例子6,class TV float price;String name;TV(String name, float price) this.name = name;this.price = price; public class Example6_6 public static void main(String args) ThreadJoin a = new ThreadJoin();a.customer.start();a.tvMaker.start(); ,class ThreadJoin implements Runnable TV
24、 tv;Thread customer, tvMaker;ThreadJoin() customer = new Thread(this);tvMaker = new Thread(this);customer.setName(“顾客“);tvMaker.setName(“电视制造厂“);public void run() if (Thread.currentThread() = customer) System.out.println(customer.getName() + “等“ + tvMaker.getName()+ “生产电视“);try tvMaker.join(); catch
25、 (InterruptedException e) ,System.out.println(customer.getName() +“买了一台电视:“ + tv.name+ “ 价钱:“ + tv.price); else if (Thread.currentThread() = tvMaker) System.out.println(tvMaker.getName() + “开始生产电视,请等.“);try Thread.sleep(2000); catch (InterruptedException e) tv = new TV(“红星牌“, 3288);System.out.printl
26、n(tvMaker.getName() + “生产完毕“); ,6.8 后台(Daemon)线程,程序中的所有用户线程都已结束运行时,即使后台线程的run方法中还有需要执行的语句,后台线程也立刻结束运行。 void setDaemon(boolean on) 一个线程调用方法可以将自己设置成一个后台线程。注意:线程必须在启动之前设置自己是否是后台线程 .,例子7,public class Example6_7 extends Thread public Example6_7 () setDaemon(true); start(); public void run() while(true) try sleep(1000); catch (InterruptedException e) System.out.println(this);public static void main(String args) for(int i = 0; i 10; i+) new Example6_7 (); ,