第11章 多线程

程序、进程、多任务、线程的概念与区别;
线程的生命周期;
创建线程的两种方法;
多线程的同步控制;
线程之间的通信。

并发执行与并行执行不同,并行执行通常表示同一时刻有多个代码在处理 器上执行,这往往需要多个处理器,如CPU等硬件的支持。而并发执行通常表示,在单处 理器上,同一时刻只能执行一个代码,但在一个时间段内,这些代码交替执行,即所 谓“微观串行,宏观并行”。

通常,一个Java程序中各个部分是按顺序依次执行的。由于某种原因,需要将 这些按顺序执行的“程序段”转成并发执行,每一个“程序段”是一个逻辑上相对完整的程序 代码段。

多线程的主要目的就是将一个程序中的各个“程序段”并发化。

在Java语言中用线 程对象来表示这些代码段,各个线程之间的并发执行,就意味着一个Java程序的各个代码 段的并发执行。

多线程 (multithread)是指在同一个进程中同时存在几个执行体,按几条不同的执行路径同时工 作的情况。所以,多线程编程的含义就是可将一个程序任务分成几个可以同时并发执行的 子任务。

程序、进程、多任务与线程
程序是静态的代码
进程是程序的一次执行过程(多次登录不同QQ是不同进程)
多任务是多进程
线程是一个程序的多块代码段,CPU在同一时间段内执行 一个程序中的多个程序段来完成工作,这就是多线程的概念。
进程是资源分配 的单位,线程是处理器调度的基本单位。

在这里插入图片描述

Java的Thread线程类与Runnable接口
【例11.1 】 利用Thread类的子类来创建线程。

package application;


public class Main {
	
	public static void main(String[] args) {

		System.out.println("开始");

		// 三个线程
		MyThread you = new MyThread("你");
		MyThread she = new MyThread("她");

		you.start();
		she.start();

		// 主线程
		for (int i = 0; i < 5; i++)
			System.out.println("主方法main()运行结束!");
	}
}

class MyThread extends Thread {
	private String who;

	public MyThread(String who) {
		this.who = who;
	}

	@Override
	public void run() {
		for (int i = 0; i < 5; i++) {
			try {
				sleep((int) (1000 * Math.random()));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(who + "正在运行!");
		}
	}
}

【例11.2 】 利用Runnable接口来创建线程。

package application;


public class Main2 implements Runnable {

	private String who;

	public Main2(String who) {
		this.who = who;
	}

	public static void main(String[] args) {

		System.out.println("开始");

		// 三个线程
		Main2 you = new Main2("你");
		Main2 she = new Main2("她");
		Thread t1 = new Thread(you);
		Thread t2 = new Thread(she);
		t1.start();
		t2.start();

		// 主线程
		for (int i = 0; i < 5; i++)
			System.out.println("主方法main()运行结束!");
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 5; i++) {
			try {
				Thread.sleep((int) (1000 * Math.random()));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(who + "正在运行!");
		}
	}
}


【例11.3 】 在多线程程序中join()方法的使用。
使得you线程先执行完后,再执行she 线程,待she线程结束后,再输出字符串“主方法main()运行结束!”。

join():语句t.join()将使t线程“加塞”到当 前线程之前获得CPU,当前线程则进入阻塞状态,直到线程t结束为止,当前线程恢复为 就绪状态,等待线程调度。

package application;

public class Main3 implements Runnable {

	private String who;

	public Main3(String who) {
		this.who = who;
	}

	public static void main(String[] args) {

		System.out.println("开始");
		Main3 you = new Main3("you");
		Main3 she = new Main3("she");
		Thread t1 = new Thread(you);
		Thread t2 = new Thread(she);
		t1.start();
		try {
			t1.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		t2.start();
		try {
			t2.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		// 主线程
		for (int i = 0; i < 5; i++)
			System.out.println("主方法main()运行结束!");

	}

	@Override
	public void run() {
		// TODO Auto-generated method stub

		for (int i = 0; i < 5; i++) {
			try {
				Thread.sleep((int) (1000 * Math.random()));
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(who + "正在运行!");
		}
	}
}

1.线程间的数据共享
【例11.4 】 用Thread子类程序来模拟航班售票系统,实现3个售票窗口发售某次航 班的10张机票,一个售票窗口用一个线程来表示。
可以通过static全局变量实现实际情况:共享

package application;

public class Main4 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		ThreadSale t1 = new ThreadSale();
		ThreadSale t2 = new ThreadSale();
		ThreadSale t3 = new ThreadSale();

		t1.start();
		t2.start();
		t3.start();
		// 每张机票均被卖了3 次,不符合实际情况
	}

}

class ThreadSale extends Thread {

	// 不是时间差的问题,是因为每个线程对象都有一个tickets变量
	private int tickets = 10;

	// static 全局变量 共享 也能实现现实实际情况
//	private static int tickets = 10;

	public void run() {
		while (tickets > 0)
			System.out.println(this.getName() + " 售机票第" + tickets-- + "号");
	}
}

【例11.5 】 用Runnable接口程序来模拟航班售票系统,利用同一可运行对象实现3 个售票窗口发售某次航班的10张机票,一个售票窗口用一个线程来表示。
满足实际情况

package application;

public class Main5 implements Runnable {

	private int tickets = 10;

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Main5 t = new Main5();// 只创建了一个可运行对象

		// 用同一个可运行对象创建了三个线程对象,实现成员变量的共享
		Thread t1 = new Thread(t, "第1售票窗口");
		Thread t2 = new Thread(t, "第2售票窗口");
		Thread t3 = new Thread(t, "第3售票窗口");
		t1.start();
		t2.start();
		t3.start();
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		while (tickets > 0)
			System.out.println(Thread.currentThread().getName() + " 售机票第" + tickets-- + "号");
	}

}

2.多线程的同步控制
【例11.6 】 设计一个模拟用户从银行取款的应用程序。设某银行账户存款额的初 值是2000元,用线程模拟两个用户分别从银行取款的情况。两个用户分4次分别从银行的 同一账户取款,每次取100元。
最终答案应为1200.

package application;

public class Main6 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//一个线程打断了另一个线程
		new Customer().start();
		new Customer().start();
	}
}

class Customer extends Thread {

	private static int sum = 2000;// static 全局

	public void run() {
		for (int i = 0; i < 4; i++) {
// 也会出错
//			System.out.println("sum=" + (sum -= 100));

			// 说明有时间差问题,即一个线程还未执行完就开始了下一个线程,导致数据错误
			int temp = sum;
			temp -= 100;
			try {
				Thread.sleep((int) (1000 * Math.random()));
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			sum = temp;
			System.out.println("sum=" + sum);

		}
	}
}

Synchronized直译为同步,但实际指的是互斥。
目的:不能使 一个线程打断了另一个线程,实现互斥

【例11.7 】 修改例11.6,用线程同步的方法设计用户从银行取款的应用程序。

package application;

public class Main7 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new Custom().start();
		new Custom().start();
	}

}

class Custom extends Thread {

	public void run() {

		for (int i = 0; i < 4; i++)
			Mbank.take(100);
	}
}

//是个必须类,实现最后结果为1200,否则会错误
class Mbank {

	private static int sum = 2000;// static 全局

	// 写个同步(互斥)方法
	// 实现一个线程不能打断另一个正在执行的线程
	// 原子
	public synchronized static void take(int k) {
		int temp = sum;
		temp -= k;
		try {
			Thread.sleep((int) (1000 * Math.random()));
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		sum = temp;
		System.out.println("sum=" + sum);
	}
}

3.线程之间的通信
例如,当一个人在排队买面包时,若她给售卖员的不是零钱,而售卖员又没有零 钱找给她,那她就必须等待,并允许她后面的人先买,以便售卖员获得零钱找给她。如果 她后面的这个人仍没零钱,那么她俩都必须等待,并允许后面的人先买。

wait()、notify()和notifyAll()只 能在同步代码块(synchronized)里调用。

【例11.8 】 用两个线程模拟存票、售票过程,但要求每存入一张票,就售出一张 票,售出后,再存入,直至售完为止。

package application;

public class Main8 {

	public static void main(String[] args) {
		// 使存票和售票两个线程对象共享一个票类对象
		Tickets t = new Tickets(10);// 票类对象

		new Producer(t).start();
		new Consumer(t).start();
	}

}

//数据和同步方法写入这个类
class Tickets {

	int size;// 票总数
	int num = 0;// 票号
	boolean available = false;// 当前是否有票可售

	public Tickets(int size) {
		this.size = size;
	}

	// 同步方法,存票
	public synchronized void put() {

		if (available)// 有票则等待
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		System.out.println("存入第" + (++num) + "号票");
		available = true;
		notify();// 唤醒售票线程
	}

	// 同步方法,售票
	public synchronized void sell() {

		if (!available)
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		System.out.println("售出第" + num + "号票");
		available = false;
		notify();// 唤醒存票线程

		if (num == size)// 设置结束标记
			num = size + 1;
	}
}

//存票线程类
class Producer extends Thread {
	private Tickets t;

	// 使存票和售票两个线程对象共享一个票类对象
	public Producer(Tickets t) {
		this.t = t;
	}

	public void run() {
		while (t.num < t.size) {
			t.put();// 原子操作
		}
	}
}

//售票线程类
class Consumer extends Thread {
	private Tickets t;

	// 使存票和售票两个线程对象共享一个票类对象
	public Consumer(Tickets t) {
		this.t = t;
	}

	public void run() {
		while (t.num <= t.size)// 注意<=
			t.sell();// 原子操作
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值