进程与线程
进程
- 动态性:存放在磁盘中的eclipse和QQ并不是进程,双击执行文件,运行之后,这才称为进程。
- 脱了进程谈论线程就没有什么意义。
线程
进程与线程
- 线程是系统中最小的执行单元。
- 同一进程有多个线程。
- 线程共享进程的资源。
线程的交互
- 线程需要通信才能正确的工作,这种通信,我们称为线程的交互。
- 交互的方式包括互斥与同步。
1. Java中的线程
程序是一段静态的代码,它是应用软件执行的蓝本。
进程是程序的一次动态执行过程,它对应了从代码加载、执行至执行完毕的一个完整过程,这个过程也是进程本身从产生、发展至消亡的过程。线程是比进程更小的执行单位,一个进程在其执行过程中,可以产生多个线程,形成多条执行线索,每条线索,即每个线程也有它自身的产生、存在和消亡的过程,也是一个动态的概念。
Java的多线程就是在操作系统每次分时给Java程序一个时间片的CPU时间内,在若干个独立的可控制的线程之间切换。
每个Java程序都有一个缺省的主线程。我们已经知道, Java应用程序总是从主类的main方法开始执行。当JVM加载代码,发现main方法之后,就会启动一个线程,这个线程称作“主线程”,该线程负责执行main方法。那么,在main方法的执行中再创建的线程,就称为程序中的其它线程。如果main方法中没有创建其他的线程,那么当main方法执行完最后一个语句,即main方法返回时,JVM就会结束我们的Java应用程序。如果main方法中又创建了其他线程,那么JVM就要在主线程和其他线程之间轮流切换,保证每个线程都有机会使用CPU资源,main方法即使执行完最后的语句,JVM也不会结束我们的程序,JVM一直要等到程序中的所有线程都结束之后,才结束我们的Java应用程序。
Java对线程的支持
Thread常用方法
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类
1.编写Thread类的子类时,需要重写父类的run方法,其目的是规定线程的具体操作,否则线程就什么也不做,因为父类的run方法中没有任何操作语句。
2.当JVM将CPU使用权切换给线程时,如果线程是Thread的子类创建的,该类中的run方法就立刻执行。
3.线程调用start() 该方法将启动线程,使之从新建状态进入就绪队列排队,一旦轮到它来享用CPU资源时,就可以脱离创建它的线程独立开始自己的生命周期了。
线程创建的两种方式2——实现Runnable接口
1.创建线程的另一个途径就是用Thread类直接创建线程对象。使用Thread创建线程对象时,通常使用的构造方法是:
- Thread(Runnable target)
- 该构造方法中的参数是一个Runnable类型的接口,因此,在创建线程对象时必须向构造方法的参数传递一个实现Runnable接口类的实例,该实例对象称作所创线程的目标对象。
2.当线程调用start方法后,一旦轮到它来享用CPU资源,目标对象就会自动调用接口中的run方法(接口回调)。
3.Thread类的run()方法与Runnable接口中的run()方法的功能和作用相同,都用来定义线程对象被调度之后所执行的操作,都是系统自动调用而用户程序不得引用的方法。系统的Thread类中,run()方法没有具体内容,所以用户程序需要创建自己的Thread类的子类,并重写run()方法来覆盖原来的run()方法。当run方法执行完毕,线程就变成死亡状态。
线程创建的两种方式3——比较
两种方式的比较
Runnable方式可以避免Thread方式由于Java单继承特性带来的缺陷。
Runnable的代码可以被多个线程(Thread实例)共享,适合于多个线程处理同一资源的情况。
模拟卖票
备注:上面是个动态图。
应用Thread模拟卖票
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();}
}
应用Runnable模拟卖票
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实例)共享,适合于多个线程处理同一资源的情况。
线程的生命周期
线程同步
问题引入
在处理多线程问题时,我们必须注意这样一个问题:当两个或多个线程同时访问同一个变量,并且一个线程需要修改这个变量。我们应对这样的问题作出处理。
在处理线程同步时,要做的第一件事就是要把修改数据的方法用关键字synchronized来修饰。
线程同步问题
线程同步是指几个线程都需要调用一个同步方法(使用关键字synchronized修饰的方法) 。
同步机制
当一个线程A使用一个synchronized修饰的方法时,其他线程想使用这个方法时就必须等待,直到线程A 使用完该方法 (除非线程A使用wait主动让出CUP资源)。
例:
假设有两个线程:会计和出纳,他俩共同拥有一个帐本 .程序要保证其中一人使用saveOrTake(int amount)时,另一个人将必须等待,即saveOrTake(int amount)方法应当是一个synchronized方法。
//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元的人民币买票。因此张飞必须等待(李逵比张飞先买了票)。
// 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();}
}
线程联合
例:使用线程联合模拟顾客等待蛋糕师制作蛋糕。
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、不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑。
// 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-EventQuecue和AWT-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()方法重新启动计时器,即恢复线程。