1、进程和线程的基础知识
在java中,程序入口被自动创建为主线程,在主线程中可以创建多个子线程。 区别: 1、是否占有资源问题 2、创建或撤销一个进程所需要的开销比创建或撤销一个线程所需要的开销大。 3、进程为重量级组件,线程为轻量级组件
2、线程的主要特点
3、线程的主要用途
4、多线程(多个线程同时运行)程序的主要优点
6.2 线程创建与启动1、与线程编程有关的一些概念 创建方式: 1 继承java.lang.Thread类 2 实现java.lang.Runnable接口 线程体:public void run()方法,其内的程序代码决定了线程的行为和功能。 线程启动: public void start () , 线程启动后,需要获取cpu才能自动调用run()运行。 线程休眠: public void sleep(long ms), 线程将暂停,放弃cpu 2、利用继承Thread类创建线程的示例 package com.px1987.j2se.thread.base; /**通过Thread类实现多线程定义一个Thread的子类并重写其run方法.*/ public class MyThread extends Thread { @Override public void run() { while (true) { System.out.println("invoke MyThread run method"); } } public static void main(String[] args) { // main方法测试线程的创建与启动 MyThread myThread = new MyThread(); // 实例化MyThread的对象 myThread.start(); // 调用myThread对象的start方法启动一个线程 } } 3、利用实现Runable接口创建线程的示例 package com.px1987.j2se.thread.base; /**通过Runable接口实现多线程定义MyRunable类实现Runnable接口,并实现接口中的run方法。*/ public class MyRunable implements Runnable { public void run() { while (true) System.out.println("invoke MyRunable run method"); } public static void main(String[] args) { // main方法测试线程的创建与启动 // 建立MyRunable类的对象,以此对象为参数建立Thread类的对象 Thread thread = new Thread(new MyRunable()); thread.start(); // 调用thread对象的start方法启动一个线程 } } 6.3 线程的状态控制1、新建状态 用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(runnable)。 2、就绪状态 处于就绪状态的线程已经具备了运行条件,但还没有分配到CPU,处于线程就绪队列,等待系统为其分配CPU。等待状态并不是执行状态,当系统选定一个等待执行的Thread对象后,它就会从等待执行状态进入执行状态,系统挑选的动作称之为“cpu调度”。一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。 3、死亡状态 死亡状态是线程生命周期中的最后一个阶段。线程死亡的原因有两个: 一个是正常运行的线程完成了它的全部工作; 另一个是线程被强制性地终止,如通过执行stop或destroy方法来终止一个线程。 Method stop() & destroy() in the class Thread is deprecated。 当一个线程进入死亡状态以后,就不能再回到其它状态了。让一个Thread对象重新执行一次的唯一方法,就是重新产生一个Thread对象。 4、体现线程状态转变的代码示例 package com.px1987.j2se.thread.base; public class MyRunable1 implements Runnable { public void run() { while (true) System.out.println("invoke MyRunable run method"); } public static void main(String[] args) { Thread thread = new Thread(new MyRunable()); // 新生状态 thread.start(); // 就绪状态,获得CPU后就能运行 try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } thread.stop(); // 死亡状态 } } 通过查API可以看到stop方法和destory方法已经过时了,所以不能再用,那要怎样做才能强制的销毁一个线程呢? 1、在run方法中执行return 线程同样结束 2、可以在while循环的条件中设定一个标志位,当它等于false的时候,while循环就不在运行,这样线程也就结束了。代码为实现的代码示例: package com.px1987.j2se.thread.StateControl; public class MyRunable2 implements Runnable { private boolean isStop; //线程是否停止的标志位 public void run() { while (!isStop) System.out.println("invoke MyRunable run method"); } public void stop(){ //终止线程 isStop=true; } public static void main(String[] args) { MyRunable myRunable=new MyRunable(); Thread thread = new Thread(myRunable); thread.start(); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } myRunable.stop(); //正确的停止线程的方法 } } 5、阻塞状态 处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或等待I/O设备等资源,将让出CPU并暂时停止自己的运行,进入阻塞状态。 在阻塞状态的线程不能进入就绪队列。只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的I/O设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续运行。有三种方法可以暂停Threads执行: (1)sleep方法 可以调用Thread的静态方法:public static void sleep(long millis) throws InterruptedException 使得当前线程休眠(暂时停止执行millis毫秒)。由于是静态方法,sleep可以由类名直接调用:Thread.sleep(…)。下面为代码示例: package com.px1987.j2se.thread.p5; import java.util.Date; import java.text.SimpleDateFormat; class SleepTest implements Runnable { private static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); public void run() { System.out.println("child thread begin"); int i = 0; while (i++ < 5) { System.out.println(format.format(new Date())); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("child thread dead at: " + format.format(new Date())); } public static void main(String[] args) { Runnable r = new SleepTest(); Thread thread = new Thread(r); thread.start(); try { Thread.sleep(20000); } catch (InterruptedException e) { e.printStackTrace(); } thread.interrupt(); System.out.println("main method dead!"); } } 该程序的运行结果如下: child thread begin 2009-02-06 04:50:29 2009-02-06 04:50:34 2009-02-06 04:50:39 2009-02-06 04:50:44 main method dead! java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at com.px1987.j2se.thread.p5.Thread4.run(Thread4.java:17) at java.lang.Thread.run(Unknown Source) 2009-02-06 04:50:49 child thread dead at: 2009-02-06 04:50:54 (2)yield方法 让出CPU的使用权,从运行态直接进入就绪态。下面为代码示例: package com.px1987.j2se.thread.StateControl; class Thread5 implements Runnable { private String name; Thread5(String s) { this.name = s; } public void run() { for (int i = 1; i <= 50; i++) { System.out.println(name + ": " + i); if (i % 10 == 0) { Thread.yield(); } } } } package com.px1987.j2se.thread.StateControl; public class YieldTest { public static void main(String[] args) { Runnable r1 = new Thread5("S1"); Runnable r2 = new Thread5("S2"); Thread t1 = new Thread(r1); Thread t2 = new Thread(r2); t1.start(); t2.start(); try { Thread.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("main method over!"); } } 该程序的部分运行结果如下: S1: 20 S2: 7 S2: 8 S2: 9 S2: 10 S1: 41 S1: 42 S1: 43 S1: 44 S1: 45 S1: 46 S1: 47 S1: 48 S1: 49 S1: 50 S2: 11 S2: 12 (3)join方法 当某个(A)线程等待另一个线程(B)执行结束后,才继续执行时,使用join方法。A的 run方法调用b.join()。下面为代码示例。 package com.px1987.j2se.thread.join; class FatherThread implements Runnable { public void run() { System.out.println("爸爸想抽烟,发现烟抽完了"); System.out.println("爸爸让儿子去买包红塔山"); Thread son = new Thread(new SonThread()); son.start(); System.out.println("爸爸等儿子买烟回来"); try { //join含义:等待son线程执行完毕,father线程才继续执行 son.join(); } catch (InterruptedException e) { System.out.println("爸爸出门去找儿子跑哪去了"); System.exit(1); } System.out.println("爸爸高兴的接过烟开始抽,并把零钱给了儿子"); } } package com.px1987.j2se.thread.join; class SonThread implements Runnable { public void run() { String tabs="\t\t\t\t\t\t"; System.out.println(tabs+"儿子出门去买烟"); System.out.println(tabs+"儿子买烟需要10分钟"); try { for (int i = 0; i < 10;) { Thread.sleep(1000); System.out.println(tabs+"儿子出去第" + ++i + "分钟"); } } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(tabs+"儿子买烟回来了"); } } package com.px1987.j2se.thread.join; public class JoinTest { public static void main(String[] args) { System.out.println("爸爸和儿子的故事"); Thread father = new Thread(new FatherThread()); father.start(); // try { // Thread.sleep(5000); // } catch (InterruptedException e) { // e.printStackTrace(); // } // father.interrupt(); } } 该程序的运行结果如下: 爸爸和儿子的故事 爸爸想抽烟,发现烟抽完了 爸爸让儿子去买包红塔山 爸爸等儿子买烟回来 儿子出门去买烟 儿子买烟需要10分钟 儿子出去第1分钟 儿子出去第2分钟 儿子出去第3分钟 儿子出去第4分钟 儿子出去第5分钟 儿子出去第6分钟 儿子出去第7分钟 儿子出去第8分钟 儿子出去第9分钟 儿子出去第10分钟 儿子买烟回来了 爸爸高兴的接过烟开始抽,并把零钱给了儿子 当时间来到儿子出去买烟的时候,Father线程调用interrupt方法就会打断son线程的正常执行,从而father线程也就不必等待son线程执行完毕再行动了,运行结果如下: 爸爸和儿子的故事 爸爸想抽烟,发现烟抽完了 爸爸让儿子去买包红塔山 爸爸等儿子买烟回来 儿子出门去买烟 儿子买烟需要10分钟 儿子出去第1分钟 儿子出去第2分钟 儿子出去第3分钟 儿子出去第4分钟 爸爸出门去找儿子跑哪去了 6.4线程的调度和优先级1、线程的基本信息
2、操作线程的基本信息代码示例 package com.px1987.j2se.thread.priority; public class ThreadInfoTest { public static void main(String[] argc) throws Exception { Runnable r = new MyThread(); Thread t = new Thread(r, "Name test"); t.start(); System.out.println("name is: " + t.getName()); Thread.currentThread().sleep(5000); System.out.println(t.isAlive()); System.out.println("over!"); } } class MyThread implements Runnable { public void run() { for (int i = 0; i < 100; i++) System.out.println(i); } } 该程序的运行结果如下: name is: Name test 0 1 2 3 . . . 97 98 99 false over! 3、线程的优先级 (1)优先级(共10级): 它们决定线程执行的先后次序(优先级高者先执行)并可以通过Thread类中的setPriority()和getPriority()方法来改变和获取优先级。典型的优先级码
(2)调度规则 Java是不支持线程时间片轮换的调度模型,而采用的是线程优先级高低的抢占调度模型。具有高优先级的线程可以抢占低优先级线程运行的机会。高优先级的线程将始终获得线程执行时间。但是这也不是绝对的,java线程调度器有可能会调用长期处于等待的线程进行执行,所以不要依靠线程的高优先级抢占模型去完成某些功能。 Java线程调度器支持不同优先级线程的抢先方式,但其本身不支持相同优先级线程的时间片轮换。但是如果java运行时系统所在的操作系统(如windows2000)支持时间片的轮换,则线程调度器就支持相同优先级线程的时间片轮换。 (3)代码示例 package com.px1987.j2se.thread.priority; public class ThreadPriorityTest { public static void main(String[] args) { Thread t1 = new Thread(new MyThread2(), "t1"); Thread t2 = new Thread(new MyThread2(), "t2"); t1.setPriority(1); t2.setPriority(10); t1.start(); t2.start(); } } class MyThread2 extends Thread { public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + ": " + i); yield(); } } } 该程序的运行结果如下: t1: 0 t2: 0 t2: 1 t2: 2 t2: 3 t2: 4 t2: 5 t2: 6 t2: 7 t2: 8 t2: 9 t1: 1 t1: 2 t1: 3 t1: 4 t1: 5 t1: 6 t1: 7 t1: 8 t1: 9 6.5线程同步互斥1、线程同步互斥的一个示例 多个线程同时访问或操作同一资源时,很容易出现数据前后不一致的问题。请看下面的例子: 男孩拿着折子去北京银行海淀分行取钱 女孩拿着男孩的银行卡去西单百货疯狂购物 男孩走到柜台钱询问帐户余额 银行的业务员小姐亲切地告诉他:"您还有10000元!"。 女孩看上了一件时髦的衣裳,准备买下 男孩在思考要取多少钱呢? 女孩到收银台准备刷卡消费 收银台刷卡机读取银行卡余额为10000元 女孩买衣服刷卡消费5000元 消费清单打印出来,消费:5000元 余额:5000元 女孩离开商场 男孩思考了1毫秒 男孩决定取5000元 银行的业务员小姐为男孩办理相关业务手续 交易完成 银行的业务员小姐告诉男孩:"您的余额为5000元"。 男孩离开银行 男孩帐户中一共有10000元,男孩拿着存折从银行取走5000元,女孩拿着男孩的银行卡购物刷卡消费5000元,最后男孩的帐户里却还剩5000元。显然这是不正确的,但是为什么会发生这样的情况呢?我们可以这样分析:男孩可以看作是一条线程,女孩也可以看作是一条线程,在同一时刻,两个线程都操作了同一个资源,那就是男孩的帐户。男孩从查看帐户余额到取走现金应该被看作是个原子性操作,是不可再分的,然而当男孩查看完余额正思考取多少钱的时候,女孩购物消费了5000元,也就是说女孩这条线程打断了男孩这条线程所要执行的任务。所以男孩刚查看完的余额10000元就不正确了,最终导致帐户中少减了5000元。 为了避免这样的事情发生,我们要保证线程同步互斥,所谓同步互斥就是:并发执行的多个线程在某一时间内只允许一个线程在执行以访问共享数据 2、Java中线程互斥的实现机制 由多线程带来的性能改善是以可靠性为代价的,所以编程出线程安全的类代码是十分必要的。当多个线程可以访问共享资源(调用单个对象的属性和方法,对数据进行读、写、修改、删除等操作)时,应保证同时只有一个线程访问共享数据,Java对此提出了有效的解决方案—同步锁。任何线程要进入同步互斥方法(访问共享资源的方法或代码段)时,就必须得到这个共享资源对象的锁,线程进入同步互斥方法后其它线程则不能再进入同步互斥方法,直到拥有共享资源对象锁的线程执行完同步互斥方法释放了锁,下一个线程才能进入同步互斥方法被执行。 Java的这一线程互斥的实现机制可以用一个最通俗的比方来说明:比如公共卫生间就是一个共享资源,每个人都可以使用,但又不能同时使用,所以卫生间里有一把锁。一个人进去了,会把门锁上,其他人就不能进去。当Ta出来的时候,要打开锁,下一个人才能继续使用。 3、利用Synchronized关键字用于修饰同步互斥方法 (1)同步互斥方法 public synchronized void method(){ //允许访问控制的代码 } (2)同步互斥代码块 synchronized(syncObject){ //允许访问控制的代码 } (3)锁定整个类 public synchronized class SyncObject{ } 由于synchronized 块可以针对任意的代码块,且可任意指定上锁的对象,因此灵活性较高。但要注意:
有了这种解决方案,我们用线程安全的代码来重新实现一下男孩和女孩取钱的故事。以下是核心代码: package com.px1987.j2se.thread.synchronous.v2; /** 帐户类 */ public class Account { /** 余额 */ private int balance; public Account(int balance) { this.balance = balance; } } package com.px1987.j2se.thread.synchronous.v2; /** 男孩类,实现Runnable接口*/ public class Boy implements Runnable { /** 银行帐户*/ Account account; public Boy(Account account) {
this.account = account; } /** 男孩拿着折子去北京银行海淀分行取钱*/ public void run() { System.out.println("男孩拿着折子去北京银行海淀分行取钱"); synchronized (account) { System.out.println("男孩走到柜台钱询问帐户余额"); int balance = account.getBalance(); System.out.println("银行的业务员小姐亲切地告诉他:\"您还有" + balance + "元!\"。"); try { System.out.println("男孩在思考要取多少钱呢?"); Thread.sleep(1); System.out.println("男孩思考了1毫秒"); } catch (InterruptedException e) { e.printStackTrace(); } int money = 5000; System.out.println("男孩决定取" + money + "元"); System.out.println("银行的业务员小姐为男孩办理相关业务手续"); account.setBalance(balance - money); System.out.println("交易完成"); System.out.println("银行的业务员小姐告诉男孩:\"您的余额为" + account.getBalance()+ "元\"。"); } System.out.println("男孩离开银行"); } } package com.px1987.j2se.thread.synchronous.v2; /** 女孩类,实现runnable接口*/ public class Girl implements Runnable { /** 女孩持有男孩的银行卡*/ Account account; public Girl(Account account) {
this.account = account; } /*** "女孩拿着小军的银行卡去西单百货疯狂购物*/ public void run() { String tabs = "\t\t\t\t\t\t"; System.out.println(tabs + "女孩拿着小军的银行卡去西单百货疯狂购物"); System.out.println(tabs + "女孩看上了一件时髦的衣裳,准备买下"); synchronized (account) { System.out.println(tabs + "女孩到收银台准备刷卡消费"); int balance = account.getBalance(); System.out.println(tabs + "收银台刷卡机读取银行卡余额为" + balance + "元"); int payout = 5000; System.out.println(tabs + "女孩买衣服刷卡消费" + payout + "元"); account.setBalance(balance - payout); System.out.println(tabs + "消费清单打印出来,消费:" + payout + "元" + " 余额:" + account.getBalance() + "元"); } System.out.println(tabs + "女孩离开商场"); } } package com.px1987.j2se.thread.synchronous.v2; public class Bank { public static void main(String[] args) { Account account=new Account(10000); Thread boyThread=new Thread(new Boy(account)); Thread girlThread=new Thread(new Girl(account)); boyThread.start(); girlThread.start(); } } 修改后的代码运行结果如下图: 男孩拿着折子去北京银行海淀分行取钱 女孩拿着小军的银行卡去西单百货疯狂购物 女孩看上了一件时髦的衣裳,准备买下 女孩到收银台准备刷卡消费 收银台刷卡机读取银行卡余额为10000元 女孩买衣服刷卡消费5000元 消费清单打印出来,消费:5000元 余额:5000元 女孩离开商场 男孩走到柜台钱询问帐户余额 银行的业务员小姐亲切地告诉他:"您还有5000元!"。 男孩在思考要取多少钱呢? 男孩思考了1毫秒 男孩决定取5000元 银行的业务员小姐为男孩办理相关业务手续 交易完成 银行的业务员小姐告诉男孩:"您的余额为0元"。 男孩离开银行 从结果中可以看出来,男孩从查看余额到取钱,女孩没有操作帐户,所以最后的余额是正确的。 4、线程死锁 使用互斥锁容易产生死锁问题。比如:一个线程需要锁定两个对象才能完成,线程1拥有对象A的锁,线程1如果再拥有对象B的锁就能完成操作,线程2拥有对象B的锁,线程2如果再拥有对象A的锁就能完成操作。 很不幸的是线程1执行不下去了,因为线程1等待的资源对象B被线程2锁住了,线程2也执行不下去了,因为线程2等待的资源对象A被线程1锁住了,这样就造成了死锁。 阅读一段文字:由多线程带来的性能改善是以可靠性为代价的,主要是因为有可能产生线程死锁。死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不能正常运行。简单的说就是:线程死锁时,第一个线程等待第二个线程释放资源,而同时第二个线程又在等待第一个线程释放资源。这里举一个通俗的例子:如在人行道上两个人迎面相遇,为了给对方让道,两人同时向一侧迈出一步,双方无法通过,又同时向另一侧迈出一步,这样还是无法通过。假设这种情况一直持续下去,这样就会发生死锁现象。 导致死锁的根源在于不适当地运用“synchronized”关键词来管理线程对特定对象的访问。“synchronized”关键词的作用是,确保在某个时刻只有一个线程被允许执行特定的代码块,因此,被允许执行的线程首先必须拥有对变量或对象的排他性访问权。当线程访问对象时,线程会给对象加锁,而这个锁导致其它也想访问同一对象的线程被阻塞,直至第一个线程释放它加在对象上的锁。 (1)死锁问题的一个代码示例 package com.px1987.j2se.thread.DeadLock; class Thread1 implements Runnable { private Object a; private Object b; public Thread1(Object a, Object b) { super(); this.a = a; this.b = b; } public void run() { synchronized (a) { System.out.println("Thread1获得对象a的锁"); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (b) { System.out.println("Thread1获得对象b的锁"); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } } } package com.px1987.j2se.thread.DeadLock; class Thread2 implements Runnable { private Object a; private Object b; public Thread2(Object a, Object b) { super(); this.a = a; this.b = b; } public void run() { synchronized (b) { System.out.println("Thread2获得对象b的锁"); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (a) { System.out.println("Thread2获得对象a的锁"); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } } } package com.px1987.j2se.thread.DeadLock; public class TestDeadLock { public static void main(String[] args) { Object a=new Object(); Object b=new Object(); Thread thread1=new Thread(new Thread1(a,b)); Thread thread2=new Thread(new Thread2(a,b)); thread1.start(); thread2.start();
} } 下面是运行结果: (2)死锁问题的另一个代码示例 package com.px1987.j2se.thread.DeadLock; public class ThreadDeadLock { public static void main(String[] args) { ThreadOne threadOne=new ThreadOne(); ThreadTwo threadTwo=new ThreadTwo(); String s1="s"; String s2="sss"; threadOne.op1=s1; threadTwo.op1=s1; threadOne.op2=s2; threadTwo.op2=s2; threadOne.start(); threadTwo.start(); } } class ThreadOne extends Thread{ String op1; String op2; public void run(){// 同步中又有同步,就可能死锁 synchronized(op1){ System.out.println(Thread.currentThread().getName()+"锁定op1"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized(op2){ System.out.println(Thread.currentThread().getName()+"锁定op2"); } } } } class ThreadTwo extends Thread{ String op1; String op2; public void run(){ synchronized(op2){ System.out.println(Thread.currentThread().getName()+"锁定op2"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized(op1){ System.out.println(Thread.currentThread().getName()+"锁定op1"); } } } } 6.6生产者消费者问题1、生产者消费者问题的示例 生产者消费者问题也是一个典型的线程问题。我们举一个这方面的实例来说明:在一个果园里,有农夫和小孩,农夫会不停的采摘水果放入果园中心的一个水果筐直到水果筐满,而小孩会不停的从水果筐里拿水果来吃,直到水果拿完。分析这个模型我们可以看出:农夫可以看成是一个生产者的线程,小孩可以看成是一个消费者的线程,而大水果筐是共享资源。 2、用Java程序表述的代码示例 package com.px1987.j2se.thread.ProducerConsumer; import java.util.Random; /*** 水果类*/ public class Fruit { /*** 水果编号*/ private int id; /*** 水果编号计数器*/ private static int number = 0; /*** 水果品种 */ private String variety; /*** 水果品种数组 */ private String[] varietys = "苹果,桃子,梨子,香蕉,西瓜,荔枝,葡萄".split(","); public Fruit() { super(); this.variety = varietys[new Random().nextInt(7)]; this.id = ++number; } } 水果筐应该设计成类似于栈的数据结构,其中包含一个数组来存放筐里的水果,而数组的下标就是水果筐的容量。设定一个索引index表示指向下一个将要放入水果的位置。类中的push方法模拟农夫向水果筐中放入水果,pop方法模拟小孩从水果筐中拿水果。这两个方法都要操作共享资源,所以push和pop方法都是同步互斥方法。 3、如何避免出现死锁 那同步的问题解决后是否会出现死锁呢?大家试想一下,如果生产的速度大于消费的速度就会导致功大于求,水果筐很容易就满了,然而生产者又一直抱着水果筐不放,没有机会给消费者使用,消费者不消费生产者就无法生产,所以就造成了死锁。 怎样解决呢?在两个同步互斥方法中用到了wait和notify方法,这两个方法是为了防止死锁的。
需要注意的是以上三个方法都只能在synchronized方法中应用,否者会出现下面的异常信息:IllegalMonitorStateException:current thread not owner。 4、实现的代码示例 package com.px1987.j2se.thread.ProducerConsumer; import java.text.DecimalFormat; import java.util.Arrays; /*** 水果框类,类似一个栈的模型 */ public class FruitBasket { /*** 容量为10的水果数组,也就是说水果框最多能放下10个水果 */ private Fruit[] fruits = new Fruit[10]; /*** 下一个将要放入水果的位置*/ private int index = 0; /*** 水果框中是否为空 @return true为空,false为不空 */ public boolean isEmpty() { return index == 0 ? true : false; } /*** 水果框是否装满* @return true为满,false为未满*/ public boolean isFull() { return index == fruits.length ? true : false; } /*** 进栈方法,模拟农夫把水果放入筐中,@param name 农夫的名字,@param fruit 水果对象 */ public synchronized void push(String name, Fruit fruit) { //用while循环,不用if,避免IndexOutOfBoundsException异常的产生 while (isFull()) { //如果水果筐满了,需要等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //将水果放入index指示的位置,index再上移一格 fruits[index++] = fruit; System.out.println(name + " 向水果框中放入编号为" + fruit.getId() + "的"+ fruit.getVariety()); display(); this.notify(); //通知其他等待的农夫或孩子可以开始工作啦 } /*** 出栈方法,模拟小孩从水果筐中取出水果,@param name 小孩的名字,@return 取出的水果*/ public synchronized Fruit pop(String name) { //用while循环,不用if,避免IndexOutOfBoundsException异常的产生 while (isEmpty()) { try { //如果水果筐空,需要等待 this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } Fruit fruit = null; fruit = fruits[--index]; //index下移一位,取出指示位置上的水果 System.out.println(name + " 从水果框中拿出编号为" + fruit.getId() + "的"+ fruit.getVariety()); display(); this.notify(); return fruit; } /*** 显示水果筐中水果存放情况*/ public void display() { for (int i = 0; i < index; i++) System.out.printf("%-10s", " NO:" + new DecimalFormat("00").format(fruits[i].getId()) + fruits[i].getVariety() + " |"); for (int i = index; i < fruits.length; i++) { System.out.printf("%-10s", " 【" + (i + 1) + "】 |"); } System.out.println(); } } package com.px1987.j2se.thread.ProducerConsumer; import java.util.Random; /** 果园里的农夫类,他是生产者,实现Runnable接口*/ public class Farmer implements Runnable { /** 姓名*/ private String name; /** 水果框*/ private FruitBasket fruitBasket; /** 农夫会不停地重复这一系列动作:从水果树上采摘一个水果放入水果框中,然后随机的休息0-2秒*/ public void run() { while (true) { fruitBasket.push(name, new Fruit()); try { Thread.sleep(new Random().nextInt(2000)); } catch (InterruptedException e) { e.printStackTrace(); } } } public Farmer(String name, FruitBasket fruitBasket) { super(); this.name = name; this.fruitBasket = fruitBasket; } } package com.px1987.j2se.thread.ProducerConsumer; import java.util.Random; /*** 果园中的小孩类,他是消费者,实现Runnable接口*/ public class Child implements Runnable { /*** 姓名*/ private String name; /*** 水果框*/ private FruitBasket fruitBasket; /*** 小孩会不停地重复这一系列动作:从水果框中拿出水果吃,然后随机休息0-5秒钟*/ public void run() { while (true) { fruitBasket.pop(name); try { Thread.sleep(new Random().nextInt(5000)); } catch (InterruptedException e) { e.printStackTrace(); } } } public Child(String name, FruitBasket fruitBasket) { super(); this.name = name; this.fruitBasket = fruitBasket; } } package com.px1987.j2se.thread.ProducerConsumer; import java.util.Random; /*** 果园中的小孩类,他是消费者,实现Runnable接口*/ public class Child implements Runnable { /*** 姓名*/ private String name; /*** 水果框*/ private FruitBasket fruitBasket; /*** 小孩会不停地重复这一系列动作:从水果框中拿出水果吃,然后随机休息0-5秒钟*/ public void run() { while (true) { fruitBasket.pop(name); try { Thread.sleep(new Random().nextInt(5)); } catch (InterruptedException e) { e.printStackTrace(); } } } public Child(String name, FruitBasket fruitBasket) { super(); this.name = name; this.fruitBasket = fruitBasket; } } 测试时使用多个生产者线程和多个消费者线程 package com.px1987.j2se.thread.ProducerConsumer; /** 果园类,测试*/ public class Orchard { public static void main(String[] args) { FruitBasket fruitBasket = new FruitBasket(); Thread farmerThread1 = new Thread(new Farmer("农夫1", fruitBasket)); Thread farmerThread2 = new Thread(new Farmer("农夫2", fruitBasket)); Thread farmerThread3 = new Thread(new Farmer("农夫3", fruitBasket)); Thread childThread1 = new Thread(new Child("小孩1", fruitBasket)); Thread childThread2 = new Thread(new Child("小孩2", fruitBasket)); Thread childThread3 = new Thread(new Child("小孩3", fruitBasket)); farmerThread1.start(); farmerThread2.start(); farmerThread3.start(); childThread1.start(); childThread2.start(); childThread3.start(); } } 程序的运行结果如下: 农夫1 向水果框中放入编号为1的苹果 NO:01苹果 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 | 农夫3 向水果框中放入编号为2的荔枝 NO:01苹果 | NO:02荔枝 | 【3】 | 【4】 | 【5】 | 【6】 | 小孩2 从水果框中拿出编号为2的荔枝 NO:01苹果 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 | 农夫2 向水果框中放入编号为3的香蕉 NO:01苹果 | NO:03香蕉 | 【3】 | 【4】 | 【5】 | 【6】 | 小孩1 从水果框中拿出编号为3的香蕉 NO:01苹果 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 | 小孩3 从水果框中拿出编号为1的苹果 【1】 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 | 农夫2 向水果框中放入编号为4的苹果 NO:04苹果 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 | 小孩1 从水果框中拿出编号为4的苹果 【1】 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 | 农夫1 向水果框中放入编号为5的苹果 NO:05苹果 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 | 农夫3 向水果框中放入编号为6的西瓜 NO:05苹果 | NO:06西瓜 | 【3】 | 【4】 | 【5】 | 【6】 | 农夫2 向水果框中放入编号为7的苹果 NO:05苹果 | NO:06西瓜 | NO:07苹果 | 【4】 | 【5】 | 【6】 | 小孩3 从水果框中拿出编号为7的苹果 NO:05苹果 | NO:06西瓜 | 【3】 | 【4】 | 【5】 | 【6】 | 小孩3 从水果框中拿出编号为6的西瓜 NO:05苹果 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 | 小孩2 从水果框中拿出编号为5的苹果 【1】 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 | 农夫2 向水果框中放入编号为8的桃子 NO:08桃子 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 | 农夫1 向水果框中放入编号为9的荔枝 NO:08桃子 | NO:09荔枝 | 【3】 | 【4】 | 【5】 | 【6】 | 农夫3 向水果框中放入编号为10的香蕉 NO:08桃子 | NO:09荔枝 | NO:10香蕉 | 【4】 | 【5】 | 【6】 | 农夫1 向水果框中放入编号为11的桃子 NO:08桃子 | NO:09荔枝 | NO:10香蕉 | NO:11桃子 | 【5】 | 【6】 | 农夫1 向水果框中放入编号为12的荔枝 NO:08桃子 | NO:09荔枝 | NO:10香蕉 | NO:11桃子 | NO:12荔枝 | 【6】 | 农夫3 向水果框中放入编号为13的西瓜 NO:08桃子 | NO:09荔枝 | NO:10香蕉 | NO:11桃子 | NO:12荔枝 | NO:13西瓜 | 小孩1 从水果框中拿出编号为13的西瓜 NO:08桃子 | NO:09荔枝 | NO:10香蕉 | NO:11桃子 | NO:12荔枝 | 【6】 | 农夫2 向水果框中放入编号为14的西瓜 NO:08桃子 | NO:09荔枝 | NO:10香蕉 | NO:11桃子 | NO:12荔枝 | NO:14西瓜 | 6.7 课后复习题1、线程的应用示例——继承Thread类 import java.util.*; public class ThreadExampleOne{ public static void main(String args[]){ TimeThread timeThread = new TimeThread(); timeThread.start(); } } class TimeThread extends Thread{ Date nowTime; public void run(){ //重写run方法 while(true){ nowTime=new Date(); System.out.println("现在的时间为:"+nowTime.getHours()+ ":"+nowTime.getMinutes()+":"+nowTime.getSeconds()); try{ sleep(1000); } catch(InterruptedException e){ System.out.println(e.toString()); } } } } 2、线程的应用示例——实现Runnable接口 import java.util.*; public class ThreadExampleTwo{ public static void main(String args[]){ TimeThread timeThreadObj = new TimeThread(); } } class TimeThread extends Object implements Runnable{ Date nowTime; Thread timeThread=null; public TimeThread(){ timeThread=new Thread(this); timeThread.start(); } public void run(){ //重写run方法 while(true){ nowTime=new Date(); System.out.println("现在的时间为:"+nowTime.getHours()+ ":"+nowTime.getMinutes()+":"+nowTime.getSeconds()); try{ timeThread.sleep(1000); } catch(InterruptedException e){ System.out.println(e.toString()); } } } } 3、线程优先级的应用示例 class ThreadPriorityDemo extends Thread{ ThreadPriorityDemo(String strName){ System.out.println("线程名称:"+strName); } public void run(){ //打印出当前线程的优先级 System.out.println(this.getPriority()); } } class PriorityDemo{ public static void main(String args[]){ ThreadPriorityDemo aThreadDemo = new ThreadPriorityDemo("Thread A"); aThreadDemo.setPriority(Thread.MAX_PRIORITY); aThreadDemo.start(); ThreadPriorityDemo bThreadDemo = new ThreadPriorityDemo("Thread B"); bThreadDemo.setPriority(Thread.MIN_PRIORITY); bThreadDemo.start(); ThreadPriorityDemo cThreadDemo = new ThreadPriorityDemo("Thread C"); cThreadDemo.setPriority(Thread.NORM_PRIORITY); cThreadDemo.start(); ThreadPriorityDemo dThreadDemo = new ThreadPriorityDemo("Thread D"); dThreadDemo.setPriority(Thread.MIN_PRIORITY); dThreadDemo.start(); ThreadPriorityDemo eThreadDemo = new ThreadPriorityDemo("Thread E"); eThreadDemo.setPriority(Thread.MIN_PRIORITY); eThreadDemo.start(); } } |
|