java之多线程
参考书目: 《java编程思想》 《java典型模块与项目实战大全》
多线程知识点 | 项目实例 |
---|---|
多线程同时结束 | 多个学生接完水一起回教室 |
线程A等待B完成 | 妈妈做菜等儿子打酱油 |
多线程安全 | 火车站售票系统 |
线程间通信 | 生产者消费者模型 |
1. 线程的五个状态
new(创建)
runable(等待运行)、 running(运行)、 blocked(暂停、挂起)
dead(结束)
它们之间的关系如图:
其中由running到runable的中间状态,即blocked(暂停,挂起)
2. 多线程一:(多个线程同时结束) 学生接完水一起回教室模型
情景描述: Water.java(水龙头对象),Student.java(学生对象),1个水龙头给多个学生接水,要求接完水所有学生一起回教室(即所有线程一起结束);
- 使用synchronized同步块;
- 使用wait()使线程挂起,等条件满足时notifyAll()唤醒所有线程;
Water.java
public class Water {
int number; // 当前已接完水的人数
public synchronized void giveWater(String name) {
synchronized (this) {
++ number;
System.out.println(name + "give water");
try {
Thread.sleep(3000); // 线程休眠3s,模拟在接水
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "finish water");
// 下面是精华,如果不满足所有学生都接完水,就将该进程挂起
if (number < 4) {
try {
wait(); // 使该线程挂起
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
notifyAll(); // 唤醒所有线程(所有人都接完水)
}
}
}
}
Student.java
public class Student extends Thread{
private String name;
private Water water;
public Student(String name, Water water) { // 构造函数
super();
this.name = name;
this.water = water;
}
public void receiveWater() {
System.out.println(name + "run to water-tap");
water.giveWater(name); // 调用接水的方法
System.out.println(name + "run back to classroom");
}
public void run() { // Thread的run()方法
receiveWater();
}
}
Main.java
public class Main {
public static void main(String[] args) {
Water water = new Water(); // 创建唯一的水龙头对象
Student s1 = new Student("1 ", water);
Student s2 = new Student("2 ", water);
Student s3 = new Student("3 ", water);
Student s4 = new Student("4 ", water);
s1.start();
s2.start();
s3.start();
s4.start();
}
}
Output:
如果不添加 if(number < 4) wait()的限制, 结果如下:
3. 多线程二:(线程A等待B完成) 妈妈等儿子打酱油模型
情景描述:Son.java(儿子对象),Mother.java(妈妈对象),妈妈做饭儿子去打酱油,妈妈必须要等儿子酱油打回来才可以炒饭;
- 使用join()方法将son线程加入mother线程,这样只有(打完酱油)son线程结束时,mother线程才会继续运行(接着做饭);
Son.java
public class Son implements Runnable{
public void run() {
System.out.println("儿子去买酱油");
try {
for (int i=1; i<=5; ++i) {
Thread.sleep(1000);
System.out.println(i + "minute");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("儿子打酱油归来");
}
}
Mother.java
public class Mother implements Runnable{
public void run() {
System.out.println("妈妈准备做饭");
System.out.println("妈妈发现没有酱油了");
System.out.println("妈妈让儿子去买酱油");
Thread son = new Thread(new Son());
son.start();
System.out.println("妈妈在等儿子回来");
try { // 合并妈妈和儿子两个线程
// 此时妈妈线程必须等儿子线程执行完毕,才可以执行
son.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("妈妈开始做饭了");
System.out.println("妈妈做好饭了");
}
}
Main.java
public class Main {
public static void main(String[] args) {
Thread monther = new Thread(new Mother());
monther.start();
}
}
Output:
妈妈准备做饭
妈妈发现没有酱油了
妈妈让儿子去买酱油
妈妈在等儿子回来
儿子去买酱油
1minute
2minute
3minute
4minute
5minute
儿子打酱油归来
妈妈开始做饭了
妈妈做好饭了
如果没有join()的限制,那么妈妈线程会在儿子线程之前执行,即没有酱油就开始做饭:
妈妈准备做饭
妈妈发现没有酱油了
妈妈让儿子去买酱油
妈妈在等儿子回来
妈妈开始做饭了
妈妈做好饭了
儿子去买酱油
1minute
2minute
3minute
4minute
5minute
儿子打酱油归来
4. 多线程之三:(多线程安全) 火车站售票系统
情景描述: Tickets.java(售票口对象),一共4张余票,但有多个售票口,存在同时多人到多个售票口买票的情况,需要保证余票>=0;
- 通过synchronized()同步方法实现
Tickets.java
public class Tickets implements Runnable {
int tickets = 4;
public void run() {
selltickets();
}
public synchronized void selltickets() {
while (true) {
if (this.tickets > 0) {
System.out.println("第"+Thread.currentThread().getName()+"号票口准备卖第"+this.tickets+"张票");
try {
System.out.println("wait for 3 minutes");
for (int i=1; i<=3; ++i){
Thread.sleep(1000);
}
System.out.println("第"+Thread.currentThread().getName()+"号票口已卖出第"+this.tickets+"张票");
this.tickets --;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
Main.java
public class Main {
public static void main(String[] args) {
Tickets ticket = new Tickets();
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket);
Thread t4 = new Thread(ticket);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
Output:
第Thread-0号票口准备卖第4张票
wait for 3 minutes
第Thread-0号票口已卖出第4张票
第Thread-0号票口准备卖第3张票
wait for 3 minutes
第Thread-0号票口已卖出第3张票
第Thread-0号票口准备卖第2张票
wait for 3 minutes
第Thread-0号票口已卖出第2张票
第Thread-0号票口准备卖第1张票
wait for 3 minutes
第Thread-0号票口已卖出第1张票
??这里代码存在问题,为什么始终只有第一个线程在运行?
5. 多线程之四:(线程间通信) 生产者消费者模型
情景描述: Producer.java(生产者对象), Consumer.java(消费者对象),StoreHouse.java(数据库对象); 生产者不断添加数据,消费者不断取出数据;如何保证读写之间的一种平衡关系?
- 问题1: Producer某条数据刚写入一半,就被Consumer取出;
- 解决方法:使用Synchronized同步块
- 问题2: 确保StoreHouse中的某条数据,Consumer重复读取多次(写速度跟不上读速度);
- 解决方法:使用轮询机制
Producer.java
public class Producer implements Runnable {
StoreHouse s; // 存储库对象
public Producer(StoreHouse s) { // 构造函数
this.s = s;
}
public void run() {
int i=0;
while (true) {
synchronized (s) { // 创建同步块
if (s.fullFlag) { // 数据库已经存满
try {
s.wait(); // 这里目的是让Consumer线程开始运行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (i == 0) {
s.name = "小明";
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
s.sex = "男";
} else {
s.name = "小红";
s.sex = "女";
}
s.fullFlag = true;
s.notifyAll(); // 唤醒线程
i = i+1;
i %= 2;
}
}
}
}
Consumer.java
public class Consumer implements Runnable {
StoreHouse s;
public Consumer(StoreHouse s) {
this.s = s;
}
public void run() {
while (true) {
synchronized (s) {
if (!s.fullFlag) { // 数据库为空
try {
s.wait(); // 这里是让Producer线程开始运行
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println("-----");
System.out.println(s.name + " " + s.sex);
s.fullFlag = false;
s.notifyAll();
}
}
}
}
}
StoreHouse.java
public class StoreHouse {
String name = "no";
String sex = "no";
boolean fullFlag = false; // 标记数据库是否存满
}
Main.java
public class Main {
public static void main(String[] args) {
StoreHouse s = new StoreHouse(); // 创建一个数据库对象
new Thread(new Producer(s)).start(); // 启动生产者线程
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Consumer(s)).start(); // 创建消费者线程
}
}
Output:
-----
小明 男
-----
小红 女
-----
小明 男
-----
小红 女
-----
小明 男
-----
小红 女
略.......