第十二章 Java多线程机制
12.1 进程与线程
- 进程:进程是程序动态执行的一个过程。
- 线程:它是最小的执行单位。
12.2 Java 中的线程
- Java 中的多线程机制:虚拟机可以自己进行多线程的切换。
- 主线程(main线程):程序总是从main方法开始执行。
- Java虚拟机会等所有线程结束之后才会结束程序(不是只结束主线程)。
12.3 线程的状态与声明周期
-
Thread.State的枚举常量有:NEW、RUNNABLE、BLOCKED、WAITNG、TIMED_WAITING或TERMINATED
-
以下列出线程的状态
- 新建状态(NEW):Thread类或其子类对象刚开辟完空间有它的内存时但没有启动。
- 可运行状态(RUNNABLE):处于NEW状态的线程使用
start()
方法之后,就是可运行状态。处于此状态就立刻执行run()方法,Thread类中run()
方法没有内容需要子类方法覆盖父类的方法。 - 中断状态(BLOCKED、WAITING、TIMED_WAITING):
- BLOCKED:当该线程被中断将CPU的使用权给其他线程的时候就是BLOCKED状态。
- TIMED_WAITING:当该线程使用
sleep(int millsecond)
后系统立即让出CPU使用权,休息millsecond毫秒之后就进入RUNNABLE。 - WAITING:线程使用
wait()
方法之后,就进入WAITING状态,进入WAITING状态的线程不会主动进入RUBBABLE装填,必须由其他线程调用notify()
方法使其进入RUNNABLE状态。另外还有一种情况是CPU执行期间使用类似于键盘输入的时候就会进入WAITING状态。
- 死亡状态:执行完
run()
方法之后。
-
例子
Example12_1.java
public class Example12_1 {
public static void main(String args[]) { //主线程
SpeakElephant speakElephant;
SpeakCar speakCar;
speakElephant = new SpeakElephant() ; //创建线程
speakCar = new SpeakCar(); //创建线程
speakElephant.start(); //启动线程
speakCar.start(); //启动线程
for(int i=1;i<=15;i++) {
System.out.print("主人"+i+" ");
}
}
}
SpeakCar.java
public class SpeakCar extends Thread {
public void run() {
for(int i=1;i<=20;i++) {
System.out.print("轿车"+i+" ");
}
}
}
SpeakElephant.java
public class SpeakElephant extends Thread {
public void run() {
for(int i=1;i<=20;i++) {
System.out.print("大象"+i+" ");
}
}
}
运行结果:没有截图结果就是没有规律的主人+数字 汽车+数字 大象+数组
12.3 Thread类与线程的创建
12.3.1 使用Thread 类的子类
- 操作
- 在Thread 子类中重写
run()
方法,在进行start()
- 在Thread 子类中重写
- 优点:可以在其子类中新增成员变量,使其具有特定的功能。但是Java不支持多继承,Thread 类的子类不能在扩展其他的子类。
12.3.2 使用Thread 类
-
使用构造方法
Thread(Runnable target)
创建线程,其中的参数是Runnable类型的接口,在线程调用start()
方法之后就执行run()
方法。所以使用这个构造方法的时候需要重写Runnable类型接口。 -
例子
Example12_2.java
public class Example12_2 {
public static void main(String args[]) {
Thread speakElephant; //用Thread声明线程
Thread speakCar; //用Thread声明线程
ElephantTarget elephant; //speakElephant线程的目标对象
CarTarget car; //speakCar线程的目标对象
elephant = new ElephantTarget();
car = new CarTarget();
speakElephant = new Thread(elephant) ; //创建线程
speakCar = new Thread(car); //创建线程
speakElephant.start(); //启动线程
speakCar.start(); //启动线程
for(int i=1;i<=15;i++) {
System.out.print("主人"+i+" ");
}
}
}
ElephantTarget.java
public class ElephantTarget implements Runnable {
public void run() {
for(int i=1;i<=20;i++) {
System.out.print("大象"+i+" ");
}
}
}
CarTarget.java
public class CarTarget implements Runnable {
public void run() {
for(int i=1;i<=20;i++) {
System.out.print("轿车"+i+" ");
}
}
}
13.3.3 目标对象与线程的关系
- 关系一:完全解耦:简单讲就是在Runnable接口外定义一个Thread类对象,来执行
run
方法。- 以上的例子就是完全解耦
- 关系二:松耦合:简单讲就是在接口内定义一个Thread类对象,使用
this
关键字来做Thread构造方法的参数。- 例子
Example12_4
public class Example12_4 {
public static void main(String args[ ]) {
House house = new House();
house.setWater(10);
house.dog.start();
house.cat.start();
}
}
House
public class House implements Runnable {
int waterAmount; //用int变量模拟水量
Thread dog,cat; //线程是目标对象的成员
House() {
dog=new Thread(this); //当前House对象作为线程的目标对象
cat=new Thread(this);
}
public void setWater(int w) {
waterAmount = w;
}
public void run() {
while(true) {
Thread t=Thread.currentThread();
if(t==dog) {
System.out.println("家狗喝水") ;
waterAmount=waterAmount-2; //狗喝的多
}
else if(t==cat){
System.out.println("家猫喝水") ;
waterAmount=waterAmount-1; //猫喝的少
}
System.out.println(" 剩 "+waterAmount);
try{ Thread.sleep(2000); //间隔时间
}
catch(InterruptedException e){}
if(waterAmount<=0) {
return;
}
}
}
}
13.3.4 线程的常用方法(略)
下边的例子主要说明对于一个变量(实体是线程)重新new一个线程,之前的线程不会被虚拟机清理。
Example12_5.java
public class Example12_5 {
public static void main(String args[]) {
Target target = new Target();
Thread thread = new Thread(target);
thread.setName("我在显示时间");
thread.start();
thread = new Thread(target);
thread.start();
}
}
Target.java
import java.time.LocalTime;
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){}
}
}
}
12.5 线程同步
- 介绍:对于虚拟机线程之间的切换是随机的,但是对于同一个数据不同线程之间是应该同步的,所以需要用到线程同步。
- 同步的方法:在变量和方法前边加上
synchronized
关键字,在用到对应的变量或者方法的时候就不会切换线程。 - 例子
- Example12_7.java
public class Example12_7 { 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(); try { Thread.sleep(1000); } catch(Exception exp){} cashier.start(); } }
- 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++) { int amountMoney = 0;//出纳取出的钱 if(money>=500) { amountMoney = amount/2; //取出amount/2 } else if(money>=400&&money<500) amountMoney= amount/3; //取出amount/3 else if(money>=200&&money<400) amountMoney = amount/5; //取出amount/5 else if(money<200) amountMoney = amount/10; //取出amount/10 money = money - Math.min(amountMoney,money); System.out.println(Thread.currentThread().getName()+ "取出"+amountMoney+"帐上有"+money+"万,休息一会再取"); try { Thread.sleep(1000); //这时会计仍不能使用saveOrTake方法 } catch(InterruptedException e){} } } } }
- Example12_7.java
12.6 协调同步的线程
- 介绍:在线程锁中,使用同步协调线程用于正在执行的线程需要其他线程辅助此线程工作,用协调同步线程暂时让出CPU使用权。
- 使用方法
- 在有
synchronized
关键字修饰的方法中使用wait()方法让出CPU使用权进入WAITING状态。 - 其他线程使用notifyAll()方法或者notify()方法使所有的WAITING状态或者部分的(随机的)线程进入执行状态。
- 在有
- 例子
- Example12_8.java
public class Example12_8 { 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(); try{ Thread.sleep(1000); } catch(Exception exp){} 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(); } }
- Example12_8.java
12.7 线程联合
- 介绍:在线程A运行期间可以让线程B一起来干活。
- 操作:
B.join()
- 注意
- 如果线程A使用join联合线程B,线程A将立即中断进行线程B,当线程B执行完成线程A才能继续排队从中断出执行。
- 如果线程B在join之前就已经执行完成的join不会有效果。
- 例子
- Example12_9 .java
public class Example12_9 { 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.setThread(customer,cakeMaker); customer.start(); } }
- ThreadJoin.java
public class ThreadJoin implements Runnable { Cake cake; Thread customer,cakeMaker; public void setThread(Thread ...t) { customer=t[0]; cakeMaker=t[1]; } public void run() { if(Thread.currentThread()==customer) { System.out.println(customer.getName()+"等待"+ cakeMaker.getName()+"制作生日蛋糕"); try{ cakeMaker.start(); cakeMaker.join(); //当前线程开始等待cakeMaker结束 } catch(InterruptedException e){} System.out.println(customer.getName()+ "买了"+cake.name+" 价钱:"+cake.price); } else if(Thread.currentThread()==cakeMaker) { System.out.println(cakeMaker.getName()+"开始制作生日蛋糕,请等..."); try { Thread.sleep(2000); } catch(InterruptedException e){} cake=new Cake("生日蛋糕",158) ; System.out.println(cakeMaker.getName()+"制作完毕"); } } class Cake { //内部类 int price; String name; Cake(String name,int price) { this.name=name; this.price=price; } } }
- Example12_9 .java