当前位置: 代码迷 >> 综合 >> 《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制
  详细解决方案

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

热度:68   发布时间:2023-10-01 10:46:03.0

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

进程与线程

进程

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

  • 动态性:存放在磁盘中的eclipse和QQ并不是进程,双击执行文件,运行之后,这才称为进程。
  • 脱了进程谈论线程就没有什么意义。

线程

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

进程与线程

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

  • 线程是系统中最小的执行单元。
  • 同一进程有多个线程。
  • 线程共享进程的资源。

线程的交互

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

  • 线程需要通信才能正确的工作,这种通信,我们称为线程的交互
  • 交互的方式包括互斥同步

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

1. Java中的线程

程序是一段静态的代码,它是应用软件执行的蓝本

进程是程序的一次动态执行过程,它对应了从代码加载、执行至执行完毕的一个完整过程,这个过程也是进程本身从产生、发展至消亡的过程。线程是比进程更小的执行单位,一个进程在其执行过程中,可以产生多个线程,形成多条执行线索,每条线索,即每个线程也有它自身的产生、存在和消亡的过程,也是一个动态的概念。

Java的多线程就是在操作系统每次分时给Java程序一个时间片的CPU时间内,在若干个独立的可控制的线程之间切换。

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

每个Java程序都有一个缺省的主线程。我们已经知道, Java应用程序总是从主类的main方法开始执行。当JVM加载代码,发现main方法之后,就会启动一个线程,这个线程称作“主线程”,该线程负责执行main方法。那么,在main方法的执行中再创建的线程,就称为程序中的其它线程。如果main方法中没有创建其他的线程,那么当main方法执行完最后一个语句,即main方法返回时,JVM就会结束我们的Java应用程序。如果main方法中又创建了其他线程,那么JVM就要在主线程和其他线程之间轮流切换,保证每个线程都有机会使用CPU资源,main方法即使执行完最后的语句,JVM也不会结束我们的程序,JVM一直要等到程序中的所有线程都结束之后,才结束我们的Java应用程序。

Java对线程的支持

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

Thread常用方法

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

void join()代表它的线程一定要等待我们的执行线程执行结束后,才会获得运行的机会;而带有参数的说明,它必要要等待的时间。

线程调用start() 该方法将启动线程,使之从新建状态进入就绪队列排队,一旦轮到它来享用CPU资源时,就可以脱离创建它的线程独立开始自己的生命周期了。

线程的其他常用方法1——run()

Thread类的run()方法与Runnable接口中的run()方法的功能和作用相同,都用来定义线程对象被调度之后所执行的操作,都是系统自动调用而用户程序不得引用的方法。系统的Thread类中,run()方法没有具体内容,所以用户程序需要创建自己的Thread类的子类,并重写run()方法来覆盖原来的run()方法。当run方法执行完毕,线程就变成死亡状态。

线程的其他常用方法2——sleep(int millsecond)

线程占有CPU期间,执行sleep方法来使自己放弃CPU资源,休眠一段时间。休眠时间的长短由sleep方法的参数决定,millsecond是毫秒为单位的休眠时间。如果线程在休眠时被打断,JVM就抛出InterruptedException异常。

因此,必须在try~catch语句块中调用sleep方法。

线程的其他常用方法3——isAlive()

线程处于“新建”状态时,线程调用isAlive()方法返回false。当一个线程调用start()方法,并占有CUP资源后,该线程的run方法就开始运行,在线程的run方法结束之前,即没有进入死亡状态之前,线程调用isAlive()方法返回true。当线程进入“死亡”状态后(实体内存被释放),线程仍可以调用方法isAlive(),这时返回的值是false。

线程的其他常用方法4——interrupt()

intertupt()方法经常用来“吵醒”休眠的线程。当一些线程调用sleep方法处于休眠状态时,一个占有CPU资源的线程可以让休眠的线程调用interrupt 方法“吵醒”自己。

线程创建的两种方式1——继承Thread类

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

1.编写Thread类的子类时,需要重写父类的run方法,其目的是规定线程的具体操作,否则线程就什么也不做,因为父类的run方法中没有任何操作语句。

2.当JVM将CPU使用权切换给线程时,如果线程是Thread的子类创建的,该类中的run方法就立刻执行。

3.线程调用start() 该方法将启动线程,使之从新建状态进入就绪队列排队,一旦轮到它来享用CPU资源时,就可以脱离创建它的线程独立开始自己的生命周期了。

线程创建的两种方式2——实现Runnable接口

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

1.创建线程的另一个途径就是用Thread类直接创建线程对象。使用Thread创建线程对象时,通常使用的构造方法是:

  • Thread(Runnable target)
  • 该构造方法中的参数是一个Runnable类型的接口,因此,在创建线程对象时必须向构造方法的参数传递一个实现Runnable接口类的实例,该实例对象称作所创线程的目标对象

2.当线程调用start方法后,一旦轮到它来享用CPU资源,目标对象就会自动调用接口中的run方法(接口回调)。

3.Thread类的run()方法与Runnable接口中的run()方法的功能和作用相同,都用来定义线程对象被调度之后所执行的操作,都是系统自动调用而用户程序不得引用的方法。系统的Thread类中,run()方法没有具体内容,所以用户程序需要创建自己的Thread类的子类,并重写run()方法来覆盖原来的run()方法。当run方法执行完毕,线程就变成死亡状态。

线程创建的两种方式3——比较

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

两种方式的比较

Runnable方式可以避免Thread方式由于Java单继承特性带来的缺陷。

Runnable的代码可以被多个线程(Thread实例)共享,适合于多个线程处理同一资源的情况。

模拟卖票

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

备注:上面是个动态图。

应用Thread模拟卖票

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

class MyThread extends Thread{private int ticketcount=5;private String name;public MyThread(String name){this.name=name;}public void run() {while(ticketcount>0){ ticketcount--;System.out.println(name + "卖掉了1张票,还剩下" +ticketcount+"张");}}}
public class Exmaple {public static void main(String[] args) {MyThread mt1=new MyThread("窗口1");MyThread mt2=new MyThread("窗口2");MyThread mt3=new MyThread("窗口3");mt1.start();mt2.start();mt3.start();}
}
package MyThread;
class MyThread extends Thread
{private int ticketcount=5;private String name;public MyThread(String name){this.name=name;}@Overridepublic void run() {while(ticketcount>0){ticketcount--;System.out.println(name + "卖掉了1张票,还剩下" + ticketcount+"张");}}}public class Exmaple {/*** @param args*/public static void main(String[] args) {// TODO Auto-generated method stubMyThread mt1=new MyThread("窗口1");MyThread mt2=new MyThread("窗口2");MyThread mt3=new MyThread("窗口3");mt1.start();mt2.start();mt3.start();}
}

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

应用Runnable模拟卖票

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

class MyRunnable implements Runnable{private int ticketcount=5;public void run() {while(ticketcount>0){ ticketcount--;System.out.println(Thread.currentThread().getName() + "卖掉了1张票,还剩下" + ticketcount+"张");}}}
public class Exmaple {public static void main(String[] args) {MyRunnable mr=new MyRunnable();Thread mt1=new  Thread(mr,"窗口1");    Thread mt2=new  Thread(mr,"窗口2");Thread mt3=new  Thread(mr,"窗口3");mt1.start();mt2.start();mt3.start();}
}
package MyRunnable;
class MyRunnable implements Runnable{private int ticketcount=5;@Overridepublic void run() {// TODO Auto-generated method stubwhile(ticketcount>0){ticketcount--;System.out.println( Thread.currentThread().getName() + "卖掉了1张票,还剩下" + ticketcount+"张");}	}}public class Example {/*** @param args*/public static void main(String[] args) {// TODO Auto-generated method stub// 3个窗口一共卖掉五张票MyRunnable mr=new MyRunnable();Thread mt1=new Thread(mr,"窗口1");Thread mt2=new Thread(mr,"窗口2");Thread mt3=new Thread(mr,"窗口3");// 3个窗口分别卖掉五张票,一共15张票/*MyRunnable mr1=new MyRunnable();MyRunnable mr2=new MyRunnable();MyRunnable mr3=new MyRunnable();Thread mt1=new Thread(mr1,"窗口1");Thread mt2=new Thread(mr2,"窗口2");Thread mt3=new Thread(mr3,"窗口3");*//*  3个窗口一共卖掉五张票Thread mt1=new Thread(new MyRunnable(),"窗口1");Thread mt2=new Thread(new MyRunnable(),"窗口2");Thread mt3=new Thread(new MyRunnable(),"窗口3");*/mt1.start();mt2.start();mt3.start();}
}

两种方式的比较

Runnable方式可以避免Thread方式由于Java单继承特性带来的缺陷。

Runnable的代码可以被多个线程(Thread实例)共享,适合于多个线程处理同一资源的情况

线程的生命周期

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

线程同步

问题引入

在处理多线程问题时,我们必须注意这样一个问题:当两个或多个线程同时访问同一个变量,并且一个线程需要修改这个变量。我们应对这样的问题作出处理。

在处理线程同步时,要做的第一件事就是要把修改数据的方法用关键字synchronized来修饰。

线程同步问题

线程同步是指几个线程都需要调用一个同步方法(使用关键字synchronized修饰的方法) 。

同步机制

当一个线程A使用一个synchronized修饰的方法时,其他线程想使用这个方法时就必须等待,直到线程A 使用完该方法 (除非线程A使用wait主动让出CUP资源)。

例:

假设有两个线程:会计和出纳,他俩共同拥有一个帐本 .程序要保证其中一人使用saveOrTake(int amount)时,另一个人将必须等待,即saveOrTake(int amount)方法应当是一个synchronized方法。

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

//Bank.java
public class Bank implements Runnable {int money=200;public void setMoney(int n) {      money=n;   }public void run() {if(Thread.currentThread().getName().equals("会计"))                saveOrTake(300);else if(Thread.currentThread().getName().equals("出纳"))         saveOrTake(150);}public  synchronized void saveOrTake(int amount) { //存取方法if(Thread.currentThread().getName().equals("会计")) {for(int i=1;i<=3;i++) { money=money+amount/3;      //每存入amount/3,稍歇一下System.out.println(Thread.currentThread().getName()+"存入"+amount/3+",帐上有"+money+"万,休息一会再存");try { Thread.sleep(1000); }        //这时出纳仍不能使用saveOrTake方法 catch(InterruptedException e){}}}else if(Thread.currentThread().getName().equals("出纳")) {for(int i=1;i<=3;i++) { //出纳使用存取方法取出60money=money-amount/3;   //每取出amount/3,稍歇一下System.out.println(Thread.currentThread().getName()+"取出"+amount/3+"帐上有"+money+"万,休息一会再取");try { Thread.sleep(1000); }      //这时会计仍不能使用saveOrTake方法catch(InterruptedException e){}}}}  
}// Example.java
public class Example {public static void main(String args[]) {Bank bank = new Bank();bank.setMoney(200);Thread accountant,           //会计cashier;       //出纳accountant = new Thread(bank);cashier = new Thread(bank);accountant.setName("会计");cashier.setName("出纳");accountant.start();cashier.start(); }
}

协调同步的线程

问题引入

对于同步方法,有时涉及到某些特殊情况,比如,当一个人在一个售票窗口排队购买电影票时,如果他给售票员的钱不是零钱,而售票员又没有零钱找给他,那么他就必须等待,并允许后面的人买票,以便售票员获得零钱给他。如果第2个人仍没有零钱,那么他俩必须等待,并允许后面的人买票。

问题解决

一个线程在使用的同步方法中时,可能根据问题的需要,必须使用wait()方法使本线程等待,暂时让出CPU的使用权,并允许其它线程使用这个同步方法。其它线程如果在使用这个同步方法时如果不需要等待,那么它用完这个同步方法的同时,应当执行notifyAll()方法通知所有的由于使用这个同步方法而处于等待的线程结束等待

  • wait()方法可以中断方法的执行,使本线程等待,暂时让出CPU的使用权,并允许其它线程使用这个同步方法。
  • notifyAll()方法通知所有的由于使用这个同步方法而处于等待的线程结束等待。曾中断的线程就会从刚才的中断处继续执行这个同步方法,并遵循“先中断先继续”的原则。
  • notify()方法只是通知处于等待中的线程的某一个结束等待。

例:

模拟两个人,张飞和李逵买电影票。售票员只有两张五元的钱,电影票5元钱一张。张飞拿二十元一张的人民币排在李逵的前面买票,李逵拿一张5元的人民币买票。因此张飞必须等待(李逵比张飞先买了票)。

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

// Example.java
public class Example {public static void main(String args[]) {TicketHouse officer = new TicketHouse();Thread zhangfei,likui;zhangfei = new Thread(officer); zhangfei.setName("张飞");likui = new Thread(officer);  likui.setName("李逵");zhangfei.start();likui.start();}
}// TicketHouse.java
public class TicketHouse implements Runnable {int fiveAmount=2,tenAmount=0,twentyAmount=0; public void run() {if(Thread.currentThread().getName().equals("张飞"))           saleTicket(20);      else if(Thread.currentThread().getName().equals("李逵"))   saleTicket(5);}private synchronized void saleTicket(int money) {if(money==5) {     //如果使用该方法的线程传递的参数是5,就不用等待fiveAmount=fiveAmount+1; System.out.println( "给"+Thread.currentThread().getName()+"入场卷,"+Thread.currentThread().getName()+"的钱正好");}else if(money==20) {           while(fiveAmount<3) {try { System.out.println("\n"+Thread.currentThread().getName()+"靠边等...");wait();       //如果使用该方法的线程传递的参数是20须等待System.out.println("\n"+Thread.currentThread().getName()+"继续买票");}catch(InterruptedException e){}}fiveAmount=fiveAmount-3;twentyAmount=twentyAmount+1;System.out.println("给"+Thread.currentThread().getName()+"入场卷,"+Thread.currentThread().getName()+"给20,找赎15元");}notifyAll();} 
}

线程联合

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

例:使用线程联合模拟顾客等待蛋糕师制作蛋糕。

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

public class Example {public static void main(String args[]) {ThreadJoin  a = new ThreadJoin();Thread customer = new Thread(a);Thread cakeMaker = new Thread(a);customer.setName("顾客");cakeMaker.setName("蛋糕师");a.setJoinThread(cakeMaker);customer.start();}
}// TicketJoin.java
public class ThreadJoin implements Runnable {Cake cake;Thread joinThread;public void setJoinThread(Thread t) {      joinThread=t;   }public void run() {if(Thread.currentThread().getName().equals("顾客")) {System.out.println(Thread.currentThread().getName()+"等待"+joinThread.getName()+"制作生日蛋糕");try{  joinThread.start();joinThread.join(); }     //当前线程开始等待joinThread结束catch(InterruptedException e){}System.out.println(Thread.currentThread().getName()+"买了"+cake.name+" 价钱:"+cake.price);      }else if(Thread.currentThread()==joinThread) {System.out.println(joinThread.getName()+"开始制作生日蛋糕,请等...");try { Thread.sleep(2000); } catch(InterruptedException e){}cake=new Cake("生日蛋糕",158) ;System.out.println(joinThread.getName()+"制作完毕");      }}   class Cake {  //内部类int price;String name;Cake(String name,int price) {this.name=name;this.price=price;} }  
}

守护线程

Java线程有两类:

用户线程:

运行在前台,执行具体的任务。

程序的主线程、连接网络的子线程。

守护线程:

运行在后台,为其他前台线程服务。

特点:一旦所有用户线程都结束运行,守护线程会随JVM结束工作。

应用:数据库连接池中的监测线程。JVM虚拟机启动后的监测线程。

如何设置守护线程

可通过调用Thread类的setDaemon(true)方法来设置当前的线程为守护线程。

注意事项

1、setDaemon(true)必须在start()方法之前调用,否则会抛出异常。

2、在守护线程中产生的新线程也是守护线程。

3、不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑。

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

《Java 2 实用教程》课程学习(12)——第12章 Java 多线程机制

// Daemon.java
public class Daemon implements Runnable {Thread A,B;Daemon() {A=new Thread(this);B=new Thread(this);}public void run() {if(Thread.currentThread()==A) {for(int i=0;i<8;i++) {System.out.println("i="+i) ;try{  Thread.sleep(1000); }catch(InterruptedException e) {}} }else if(Thread.currentThread()==B) {while(true) {System.out.println("线程B是守护线程 "); try{  Thread.sleep(1000); }catch(InterruptedException e){}}}}    
}public class Example {public static void main(String args[]) {Daemon  a=new Daemon ();a.A.start();a.B.setDaemon(true);a.B.start();}
}

GUI线程

程序包含图形用户界面(GUI)时,Java虚拟机在运行应用程序时会自动启动更多的线程,其中有两个重要的线程:AWT-EventQuecueAWT-Windows

AWT-EventQuecue ()线程负责处理GUI事件,AWT-Windows线程负责将窗体或组件绘制到桌面。

计时器线程Timer

使用Timer类的构造方法:Timer(int a, Object b)创建一个计时器。

参数a的单位是毫秒,确定计时器每隔a 毫秒"震铃"一次,参数b是计时器的监视器。计时器发生的震铃事件是ActinEvent 类型事件。

当震铃事件发生时,监视器就会监视到这个事件,就会执行接口ActionListener中的方法:

actionPerformed(Actionevent e)

使用Timer类的start()方法启动计时器,即启动线程。使用stop()方法停止计时器,即挂起线程,使用restart()方法重新启动计时器,即恢复线程。

  相关解决方案