笔记汇总:《Java面向对象程序设计》学习笔记
Java 语言的一大特点就是内置了对多线程的支持。
多线程是指同时存在几个执行体,按几条不同的执行线索共同工作的情况。
我们感觉线程正在同步执行,但并不是真的同时执行多个线程,只是Java 快速地从一个线程切换到另一个线程。
Java 虚拟机( JVM )负责管理线程。
(个人理解是,你可以把多线程当作:一个线程运行一下然后等待一会,另一个线程运行一下然后等待一会,继续下去。但因为速度快,看着感觉好像是很多线程一起运行)
程序
进程
进程是程序的一次动态执行过程。
os 可以管理多个进程,即让多个进程都机会使用系统的资源,比如 CPU 资源。
(一个 OS 里可以多个进程)
线程
其他
Java 语言使用 Thread 类及其子类的对象来表示线程。
使用 Thread 类及其子类的对象来表示线程。
Thread 提供 getstate() 方法返回枚举类型 Thread.State 的下列枚举常量之一:
NEW , RUNNABLE , BLOCKED , WAITING , TIMED_WAITING , TERMINATED
新建状态( NEW )
当一个 Thread 类或其子类的对象被声明并创建时,新生的线程对象处于NEW状态,称作新建状态。
即尚未启动(没有调用 start() 方法)的线程处于此状态。
可运行状态 (RUNNABLE)
处于 NEW 状态的线程,调用 Thread 类提供的 start() 方法,进入 RUNNABLE状态。
NEW 状态线程调用 start() ,让自己进入 RUNNABLE状态。
JVM 就会知道又有一个新一个线程排队等候切换了。
当 JVM 将 CPU 使用权切换给 RUNNABLE 状态的线程时,如果线程是 Thread 的子类创建的,该类中的 run() 方法就立刻执行。
如果线程是 Thread 的子类创建的,该类中的 run () 方法就立刻执行。所以我们必须在子类中重写父类的 run() 方法。
中断状态 (BLOCKED 、 WAITING 、 TIMED_WAITING)阻塞、等待、有限时间等待
死亡状态 (TERMINATED)终止
注
在主线程中用 Thread 的子类创建了两个线程,这两个线程在命令行窗口分别输出 5 句“老虎”和“小猫”;主线程在命令行窗口输出 6 句“主人”。
注意,程序在不同的计算机上运行或在同一台计算机上反复运行的结果不尽相同。
public class Tiger extends Thread {
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.print("|老虎" + i);
try {
sleep(1000); // 状态:TIMED_WAITING
} catch (Exception exp) {
}
}
}
}
public class Cat extends Thread {
public void run() {
for (int i = 1; i <= 5; i++) {
// System.out.print("|小猫"+i+"状态:"+getState()+"|");
System.out.print("|小猫" + i);
}
}
}
public class Example15_1 {
public static void main(String args[]) { // 主线程
Tiger tiger;
Cat cat;
tiger = new Tiger(); // 创建线程
cat = new Cat(); // 创建线程
System.out.println("tiger的状态:" + tiger.getState());
System.out.println("cat状态:" + cat.getState());
tiger.start(); // 启动线程
cat.start(); // 启动线程
for (int i = 1; i <= 6; i++) {
System.out.printf("\n%s", "tiger状态:" + tiger.getState());
System.out.printf("\n%s", "cat状态:" + cat.getState());
System.out.printf("\n%s", "主人" + i);
}
System.out.printf("\n%s", "|tiger的状态:" + tiger.getState());
System.out.printf("\n%s", "|cat状态:" + cat.getState());
}
}
输出(不唯一,每次线程顺序都可能不一样)
tiger的状态:NEW
cat状态:NEW
tiger状态:RUNNABLE|小猫1|老虎1|小猫2
cat状态:BLOCKED|小猫3
主人1|小猫4
tiger状态:TIMED_WAITING|小猫5
cat状态:RUNNABLE
主人2
tiger状态:TIMED_WAITING
cat状态:TERMINATED
主人3
tiger状态:TIMED_WAITING
cat状态:TERMINATED
主人4
tiger状态:TIMED_WAITING
cat状态:TERMINATED
主人5
tiger状态:TIMED_WAITING
cat状态:TERMINATED
主人6
|tiger的状态:TIMED_WAITING
|cat状态:TERMINATED|老虎2|老虎3|老虎4|老虎5
处于就绪状态的线程首先进入就绪队列排队等候 CPU 资源,同一时刻在就绪队列中的线程可能有多个。
Java 虚拟机中的线程调度器负责管理线程。
在采用时间片的系统中,每个线程都有机会获得 CPU 的使用权,以便使用 CPU 资源执行线程中的操作。
当线程使用 CPU 资源的时间到了后,即使线程没有完成自己的全部操作, Java 调度器也会中断当前线程的执行,把 CPU 的使用权切换给下一个排队等待的线程,当前线程将等待 CPU 资源的下一次轮回,然后从中断处继续执行。
Java 调度器的任务是使高优先级的线程能始终运行,一旦时间片有空闲,则使具有同等优先级的线程以轮流的方式顺序使用时间片。
在实际编程时,不提倡使用线程的优先级来保证算法的正确执行。
用 Thread 类或子类创建线程对象。
在编写 Thread 类的子类时,需要重写父类的 run() 方法,其目的是规定线程的具体操作,否则线程就什么也不做,因为父类的 run() 方法中没有任何操作语句。
例子
public class People extends Thread {
StringBuffer str;
People(String s,StringBuffer str) {
setName(s); //调用从Thread类继承的setName方法为线程起名字
this.str=str;
}
public void run() {
for(int i=1;i<=3;i++) {
str.append(getName()+","); //将当前线程的名字尾加到str 这里有可能被中断
System.out.println("我是"+getName()+",字符串为:"+str);
try { sleep(1000); // 中断状态 (TIMED_WAITING)
}
catch(InterruptedException e){}
}
}
}
public class Example15_2 {
public static void main(String args[]) {
People personOne,personTwo;
StringBuffer str=new StringBuffer();
personOne=new People("张三",str);
personTwo =new People("李四",str);
personOne.start();
personTwo.start();
}
}
输出(不唯一,每次线程顺序都可能不一样)
我是张三,字符串为:张三,李四,
我是李四,字符串为:张三,李四,
我是李四,字符串为:张三,李四,李四,张三,
我是张三,字符串为:张三,李四,李四,张三,
我是李四,字符串为:张三,李四,李四,张三,李四,张三,
我是张三,字符串为:张三,李四,李四,张三,李四,张三,
使用 Thread 子类创建线程的优点是:我们可以在子类中增加新的成员变量,使线程具有某种属性,也可以在子类中新增加方法,使线程具有某种功能。
但是, Java 不支持多继承, Thread 类的子类不能再扩展其他的类。
使用 Thread 创建线程对象时,通常使用的构造方法是:
Thread ( Runnable target )
例子
public class Bank implements Runnable {
private int number=0;
public void setMoney(int m) {
number=m;
}
public void run() { //重写Runnable接口中的方法
while(true) {
String name=Thread.currentThread().getName();
if(name.equals("One")) {
if(number<=160) {
System.out.println(name+"进入死亡状态");
return; // threadOne的run方法结束
}
number=number+10; // 可能被中断
System.out.println("我是"+name+"现在number="+number);
}
if(Thread.currentThread().getName().equals("Two")) {
if(number<=0) {
System.out.println(name+"进入死亡状态");
return; // threadTwo的run方法结束
}
number=number-100; // 可能被中断
System.out.println("我是"+name+"现在number="+number);
}
try{ Thread.sleep(800);
}
catch(InterruptedException e){}
}
}
}
public class Example15_3 {
public static void main(String args[ ]) {
Bank bank=new Bank();
bank.setMoney(300);
Thread threadOne,threadTwo;
threadOne=new Thread(bank);
threadOne.setName("One");
threadTwo=new Thread(bank); //threadTwo和 threadOne的目标对象相同
threadTwo.setName("Two");
threadOne.start();
threadTwo.start();
}
}
输出(不唯一,每次线程顺序都可能不一样)
我是One现在number=310
我是Two现在number=210
我是One现在number=220
我是Two现在number=220
我是Two现在number=120
One进入死亡状态
我是Two现在number=20
我是Two现在number=-80
Two进入死亡状态
具有相同目标对象的线程,当其中一个线程享用 CPU 资源时,目标对象自动调用接口中的 run 方法,这时, run 方法中的局部变量被分配内存空间
当轮到另一个线程享用 CPU 资源时,目标对象会再次调用接口中的 run 方法,那么, run() 方法中的局部变量会再次分配内存空间。
也就是说 run() 方法己经启动运行了两次,分别运行在不同的线程中,即运行在不同的时间片内。
不同线程的 run() 方法中的局部变量互不干扰,一个线程改变了自己的 run() 方法中局部变量的值不会影响其他线程的 run() 方法中的局部变量。
在线程中启动其它线程
public class Move implements Runnable {
public void run() {
String name=Thread.currentThread().getName(); //局部变量name
StringBuffer str=new StringBuffer(); //局部变量str
for(int i=1;i<=3;i++) { //局部变量i
if(name.equals("张三")) {
str.append(name);
System.out.println(name+"线程的局部变量i="+i+",str="+str);
}
else if(name.equals("李四")) {
str.append(name);
System.out.println(name+"线程的局部变量i="+i+",str="+str);
}
try{ Thread.sleep(800);
}
catch(InterruptedException e){}
}
}
}
public class Example15_4 {
public static void main(String args[]) {
Move move=new Move();
Thread zhangsan,lisi;
zhangsan=new Thread(move);
zhangsan.setName("张三");
lisi=new Thread(move);
lisi.setName("李四");
zhangsan.start();
lisi.start();
}
}
输出(唯一,不同线程的 run() 方法中的局部变量互不干扰)
李四线程的局部变量i=1,str=李四
张三线程的局部变量i=1,str=张三
李四线程的局部变量i=2,str=李四李四
张三线程的局部变量i=2,str=张三张三
李四线程的局部变量i=3,str=李四李四李四
张三线程的局部变量i=3,str=张三张三张三
线程通过调用 start() 方法启动,使其从新建状态进入就绪队列排队,一旦轮到它来享用CPU 资源,就可以脱离创建它的主线程独立开始自己的生命周期了。
前面的例子都是在主线程中启动其他线程。实际上也可以在任何一个线程中启动另外一个线程。
在线程中启动其它线程
public class ComputerSum implements Runnable {
int i=1,sum=0; //线程共享的数据
public void run() {
Thread thread=Thread.currentThread();
System.out.println(thread.getName()+"开始计算");
while(i<=10) {
sum=sum+i;
System.out.print(" "+sum);
if(i==5) {
System.out.println(thread.getName()+"完成任务了!i="+i);
Thread threadTwo=new Thread(this);//threadTwo与 threadOne的目标对象相同
threadTwo.setName("李四");
threadTwo.start(); //启动threadTwo
i++; //死亡之前将i变成6
return; //threadOne死亡
}
i++;
try{ Thread.sleep(300);
}
catch(InterruptedException e){}
}
}
}
public class Example15_5 {
public static void main(String args[]) {
ComputerSum computer=new ComputerSum();
Thread threadOne;
threadOne=new Thread(computer);
threadOne.setName("张三");
threadOne.start();
}
}
输出
张三开始计算
1 3 6 10 15张三完成任务了!i=5
李四开始计算
21 28 36 45 55
线程调用该方法将启动线程,使之从新建状态进入就绪队列排队,
一旦轮到它来享用 CPU 资源时,就可以脱离创建它的线程独立开始自己的生命周期了。
Thread 类的 run() 方法与 Runnab1e 接口中的 run() 方法的功能和作用相同,都用来定义线程对象获得 CPU 资源后所执行的操作,都是系统自动调用而无需要用户调用的方法。
用户程序需要重写 run() 方法,定义线程需要完成的任务。
例子
public class E {
public static void main(String[] args) {
Target target = new Target();
// target.run();
Thread thread = new Thread(target);
thread.start(); // 如果注释掉,就是没有调用start() 并没有成为真正的线程,只是新建状态
for (int i = 0; i < 3; i++) {
System.out.println("yes");
try {
Thread.sleep(1000);
} catch (InterruptedException exp) {
}
}
}
}
class Target implements Runnable {
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("ok");
try {
Thread.sleep(1000);
} catch (InterruptedException exp) {
}
}
}
}
输出
yes
ok
yes
ok
yes
ok
主线程在 main 方法或用户线程在它的 run() 方法中调用 sleep 方法 (Thread 类提供的 static 方法)放弃 CPU 资源,休眠一段时间。
如果线程在休眠时被打断, JVM 就抛出 InterruptedException 异常。
因此,必须在 try~ catch 语句块中调用 sleep 方法。
(NEW和TERMINATED时返回false,否则返回true)
需要注意的是,一个己经运行的线程在没有进入 TERMINATED (死亡)状态时,不要再给线程分配实体,
由于线程只能引用最后分配的实体,先前的实体就会成为“垃圾”并且不会被垃圾收集机收集掉
线程每隔 1 s 在命令行窗口输出本地机器的时间,
该线程又被分配了实体,新实体又开始运行。
因为垃圾实体仍然在工作,因此,在命令行每秒钟能看见两行同样的本地机器时间。
import java.time.*;
public class Target implements Runnable {
public void run() {
while(true) {
LocalTime time = LocalTime.now();
System.out.printf("%d:%d:%d\n",
time.getHour(),time.getMinute(),time.getSecond());
try{ Thread.sleep(1000);
}
catch(InterruptedException e){}
}
}
}
public class Example15_6 {
public static void main(String args[]) {
Target target = new Target();
Thread thread = new Thread(target);
thread.start();
thread = new Thread(target); // 新实体,老实体成了垃圾实体
thread.start();
}
}
输出(不唯一,程序运行时时间不一样)
17:37:3
17:37:3
17:37:4
17:37:4
17:37:5
17:37:5
17:37:6
17:37:6
··· // 无限输出下去
currentThread() 方法是 Thread 类中的 static 方法,可以用类名调用,该方法返回当前正在使用 CPU 资源的线程的引用。
interrupt 方法经常用来“吵醒”休眠的线程。
当一些线程调用 sleep 方法处于休眠状态时,一个占有 CPU 资源的线程可以让休眠的线程调用 interrupt() 方法“吵醒”自己,
即导致休眠的线程发生 InterruptedException 异常,从而结束休眠,进人RUNNABLE 状态,重新排队等待 CPU 资源。
有两个线程: student 和 teacher ,
其中,student 准备睡一小时后再开始上课, teacher 在输出 3 句“上课”后,吵醒休眠的线程 student。
public class ClassRoom implements Runnable {
Thread student,teacher;
ClassRoom() {
teacher=new Thread(this);
student=new Thread(this);
teacher.setName("雷老师");
student.setName("张爱睡");
}
public void run() {
if(Thread.currentThread()==student) {
try{ System.out.println(student.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);
}
catch(InterruptedException e){}
}
student.interrupt(); //吵醒student
}
}
}
public class Example15_7 {
public static void main(String args[]) {
ClassRoom room=new ClassRoom();
room.student.start();
room.teacher.start();
}
}
输出(不唯一,每次线程顺序都可能不一样)
上课!
张爱睡正在睡觉,不听课
上课!
上课!
张爱睡被老师叫醒了
张爱睡开始听课
应该不考
问题:当两个或多个线程同时访问同一个变量,并且一个线程需要修改这个变量。我们应对这样的问题作出处理,否则可能发生混乱
有两个线程,即会计和出纳,它们共同拥有一个账本。
它们都可以使用存取方法对账本进行访问,在会计使用存取方法时,向账本上写入存钱记录;在出纳使用存取方法时,向账本写入取钱记录。
因此,当会计正在使用账本时,出纳被禁止使用,反之亦然。
例如,
会计使用账本时,在账本上存入 270 万元,但在存入这笔钱时,每存入 90 万元就喝口茶,那么会计喝茶休息时(注意,这时存钱这件事还没结束,即会计还没有使用完存取方法),出纳仍不能使用账本;
出纳使用账本时,在账本上取出 60 万元,但在取出这笔钱时,每取出 30 万元就喝口茶,那么出纳喝茶休息时,会计不能使用账本。
也就是说,程序要保证其中一个人使用账本时,另一个人必须等待。
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class WindowMoney extends JFrame implements ActionListener {
JTextArea text1,text2;
Bank bank;
Thread 会计,出纳;
JButton startShowing=new JButton("开始演示");
WindowMoney() {
bank=new Bank();
会计=new Thread(bank);
出纳=new Thread(bank);
会计.setName("会计");
出纳.setName("出纳");
text1=new JTextArea(5,16);
text2=new JTextArea(5,16);
bank.setShowText(text1,text2);
bank.setMoney(100);
setLayout(new FlowLayout());
add(startShowing);
add(new JScrollPane(text1));
add(new JScrollPane(text2));
setVisible(true);
setSize(570,300);
startShowing.addActionListener(this);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void actionPerformed(ActionEvent e) {
if(!(出纳.isAlive())&&!(会计.isAlive())) {
会计=new Thread(bank);
出纳=new Thread(bank);
会计.setName("会计");
出纳.setName("出纳");
bank.setMoney(100);
text1.setText(null);
text2.setText(null);
}
try{
会计.start();
出纳.start();
}
catch(Exception exp){}
}
}
import javax.swing.*;
public class Bank implements Runnable {
int money=100;
String name;
JTextArea text1,text2;
public void setShowText(JTextArea t1,JTextArea t2) {
text1=t1;
text2=t2;
}
public void setMoney(int n) {
money=n;
}
public synchronized void 存取(int number) { //存取方法
if(name.equals("会计")) {
for(int i=1;i<=3;i++) { //会计使用存取方法存入270,存入90,稍歇一下
money=money+number;
text1.append("帐上有"+money+"万,休息一会再存\n");
try { Thread.sleep(1000); //这时出纳仍不能使用存取方法
} //因为会计还没使用完存取方法
catch(InterruptedException e){}
}
}
else if(name.equals("出纳")) {
for(int i=1;i<=2;i++) { //出纳使用存取方法取出60,取出30,稍歇一下
money=money-number;
text2.append("帐上有"+money+"万,休息一会再取\n");
try { Thread.sleep(1000); //这时会计仍不能使用存取方法
} //因为出纳还没使用完存取方法
catch(InterruptedException e){}
}
}
}
public void run() {
name=Thread.currentThread().getName();
if(name.equals("会计"))
存取(90);
else if(name.equals("出纳"))
存取(30);
}
}
public class Example15_10 {
public static void main(String args[]) {
WindowMoney win=new WindowMoney();
win.setTitle("会计与出纳");
}
}
程序运行效果(假装下面是个窗体)
帐上有190万,休息一会再存 帐上有280万,休息一会再存 帐上有370万,休息一会再存 | 帐上有340万,休息一会再取 帐上有310万,休息一会再取 |
---|
当一个线程正在使用一个同步方法时(用 synchronized 修饰的法) ,其它线程就不能使用这个同步方法。
对于同步方法,有时涉及到某些特殊情况,
比如当你在一个售票窗口排队购买电影票时,
如果你给售票员的钱不是零售票员又没有零钱找给你,那么你就必须等待,允许你后面的人买票,以便售票员获得零钱给你。
如果第 2 个人仍没有零钱,那么你俩必须等待,并允许后面的人买票。
一个线程使用的同步方法中用到某个变量,此变量又需要其它线程修改后才能符合本线程的需要,那么可以在同步方法中使用 wait() 方法。
wait() 、 notify() 和 notifyAll() 都是 Object 类中的 final 方法,被所有的类继承、且不允许重写的方法。
为了避免复杂的数学算法,我们模拟张平和李明两个人买电影票,
售票员只有两张 5 元的人民币,电影票 5 元一张。
张平拿一张 20 元的人民币排在李明的前面买票,李明拿一张 5 元的人民币买票,因此张平必须等待。
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class WindowTicket extends JFrame implements Runnable,ActionListener {
SellTicket ticketAgent;
Thread 张平,李明;
static JTextArea text;
JButton startBuy=new JButton("开始买票");
WindowTicket() {
ticketAgent=new SellTicket(); //售票员;
张平=new Thread(this);
张平.setName("张平");
李明=new Thread(this);
李明.setName("李明");
text=new JTextArea(10,30);
startBuy.addActionListener(this);
add(text,BorderLayout.CENTER);
add(startBuy,BorderLayout.NORTH);
setVisible(true);
setSize(360,300);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void actionPerformed(ActionEvent e) {
try{ 张平.start();
李明.start();
}
catch(Exception exp) {}
}
public void run() {
if(Thread.currentThread()==张平) {
ticketAgent.售票规则(20);
}
else if(Thread.currentThread()==李明) {
ticketAgent.售票规则(5);
}
}
}
public class SellTicket {
int 五元钱的个数=2,十元钱的个数=0,二十元钱的个数=0;
String s=null;
public synchronized void 售票规则(int money) {
String name=Thread.currentThread().getName();
if(money==5) { //如果使用该方法的线程传递的参数是5,就不用等待
五元钱的个数=五元钱的个数+1;
s= "给"+name+"入场卷,"+name+"的钱正好";
WindowTicket.text.append("\n"+s);
}
else if(money==20) {
while(五元钱的个数<3) {
try { WindowTicket.text.append("\n"+name+"靠边等...");
wait(); //如果使用该方法的线程传递的参数是20须等待
}
catch(InterruptedException e){}
}
五元钱的个数=五元钱的个数-3;
二十元钱的个数=二十元钱的个数+1;
s="给"+name+"入场卷,"+name+"给20,找赎15元";
WindowTicket.text.append("\n"+s);
}
notifyAll();
}
}
public class Example15_11 {
public static void main(String args[]) {
WindowTicket win=new WindowTicket();
win.setTitle("请排队买票");
}
}
程序运行效果(假装下面是个窗体)
张平靠边等… 给李明入场卷,李明的钱正好 给张平入场卷,张平给20,找赎15元 |
---|
用两个线程来实现猜数字游戏,
第一个线程负责随机给出 0 ~ 99 的一个整数,第二个线程负责猜出这个数,
每当第二个线程给出自己的猜测后,第一个线程会提示“你猜小了”“你猜大了”或“恭喜,你猜对了”。
第二个线程首先要等待第一个线程设置好要猜测的数。
在第一个线程设置好猜测数之后,两个线程还要互相等待,其原则是第二个线程给出自己的猜测后,等待第一个线程给出的提示;
第一个线程给出提示后,等待第二个线程给出猜测,
如此进行,直到第二个线程给出正确的猜测,两个线程进入死亡状态。程序运行效
public class Number implements Runnable {
final int SMALLER=-1,LARGER=1,SUCCESS=8;
int realNumber,guessNumber,min=0,max=100,message=SMALLER;
boolean pleaseGuess=false,isGiveNumber=false;
Thread giveNumberThread,guessNumberThread;
Number() {
giveNumberThread=new Thread(this);
guessNumberThread=new Thread(this);
}
public void run() {
for(int count=1;true;count++) {
setMessage(count);
if( message==SUCCESS)
return;
}
}
public synchronized void setMessage(int count) {
if(Thread.currentThread()==giveNumberThread&&isGiveNumber==false) {
realNumber=(int)(Math.random()*100);
System.out.println("随机给你一个0至99之间的数,猜猜是多少?");
isGiveNumber=true;
pleaseGuess=true;
}
if(Thread.currentThread()==giveNumberThread) {
while(pleaseGuess==true)
try { wait(); //让出CPU使用权,让另一个线程开始猜数
}
catch(InterruptedException e){}
if(realNumber>guessNumber) { //结束等待后,根据另一个线程的猜测给出提示
message=SMALLER;
System.out.println("你猜小了");
}
else if(realNumber
输出(不唯一,每次的数都可能不一样)
随机给你一个0至99之间的数,猜猜是多少?
我第1次猜这个数是:50
你猜大了
我第2次猜这个数是:25
你猜大了
我第3次猜这个数是:12
你猜大了
我第4次猜这个数是:6
你猜小了
我第5次猜这个数是:9
恭喜,你猜对了
应该不考
一个线程 A 在占有 CPU 资源期间,可以让其他线程调用 join() 和本线程联合,
例如:
b.join();
称 A 在运行期间联合了 B。
如果线程 A 在占有 CPU 资源期间联合了线程 B ,
那么线程 A 将立刻中断执行,一直等到它联合的线程 B 执行完毕,
线程 A 再重新排队等待 CPU 资源,以便恢复执行。
如果 A 准备联合的线程 B 已经结束,那么 B.join() 不会产生任何效果
一个线程在运行期间联合了另外个线程
public class ThreadJoin implements Runnable {
Car car;
Thread customer,carMaker;
ThreadJoin() {
customer=new Thread(this);
customer.setName("顾客");
carMaker=new Thread(this);
carMaker.setName("汽车制造厂");
}
public void run() {
if(Thread.currentThread()==customer) {
System.out.println(customer.getName()+"等"+carMaker.getName()+"生产汽车");
try{ carMaker.start();
carMaker.join(); //线程customer开始等待carMaker结束
}
catch(InterruptedException e){}
System.out.println(customer.getName()+
"买了一辆汽车:"+car.name+" 价钱:"+car.price);
}
else if(Thread.currentThread()==carMaker) {
System.out.println(carMaker.getName()+"开始生产汽车,请等...");
try { carMaker.sleep(2000);
}
catch(InterruptedException e){}
car=new Car("红旗轿车",288000) ;
System.out.println(carMaker.getName()+"生产完毕");
}
}
}
public class Car {
float price;
String name;
Car(String name,float price) {
this.name=name;
this.price=price;
}
}
public class Example15_14 {
public static void main(String args[]) {
ThreadJoin a=new ThreadJoin();
a.customer.start();
}
}
输出(不唯一,每次线程顺序都可能不一样)
顾客等汽车制造厂生产汽车
汽车制造厂开始生产汽车,请等...
汽车制造厂生产完毕
顾客买了一辆汽车:红旗轿车 价钱:288000.0
线程默认是非守护线程,非守护线程也称用户(user)线程,
一个线程调用void setDaemon(boolean on)方法可以将自己设置成一个守护(daemon)线程。
例如:
thread.setDaemon(true)
当程序中的所有用户线程都结束运行时,即使守护线程的 run() 方法中还有需要执行的语句,守护线程也立刻结束运行。
可以用守护线程做一些不是很严格的工作,线程的随时结束不会产生什么不良的后果。
注意,一个线程必须在运行之前设置自己是否是守护线程。
守护线程
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 Example15_15 {
public static void main(String args[]) {
Daemon a=new Daemon ();
a.A.start();
a.B.setDaemon(true);
a.B.start();
}
}
输出(不唯一,多出的 线程B是守护线程 可能出现在其他地方)
i=0
线程B是守护线程
线程B是守护线程
i=1
线程B是守护线程
i=2
线程B是守护线程
i=3
线程B是守护线程
i=4
线程B是守护线程
i=5
线程B是守护线程
i=6
线程B是守护线程
i=7
线程B是守护线程
同志们,欢呼吧,《Java面向对象程序设计》到这就结束了!
休息一天准备继续《算法设计与分析》的学习吧!