目录
售票系统
我们先来看看一个日常生活中的售票,日常生活中有需要用到售票的地方,但是并没有那么简单,看一段模拟售票的代码
代码演示:
注意:下面这个模拟的售票系统,是会出现超卖的情况的,因为我们没有做线程同步的处理,因此就会发生票数超卖的情况,在现实生活中,很明显这样是不可以的,所以后面我们就会对该售票系统进行处理,处理超卖的情况
package idea.chapter17.ticket;
/**
* 使用多线程,模拟三个窗口同时售票100张
*/
public class SellTicket {
public static void main(String[] args) {
//测试
// SellTicket01 sellTicket01 = new SellTicket01();
// SellTicket01 sellTicket02 = new SellTicket01();
// SellTicket01 sellTicket03 = new SellTicket01();
//
// //这里我们会出现超卖..
// sellTicket01.start();//启动售票线程
// sellTicket02.start();//启动售票线程
// sellTicket03.start();//启动售票线程
System.out.println("===使用实现接口方式来售票=====");
SellTicket02 sellTicket02 = new SellTicket02();
new Thread(sellTicket02).start();//第1个线程-窗口
new Thread(sellTicket02).start();//第2个线程-窗口
new Thread(sellTicket02).start();//第3个线程-窗口
}
}
//使用继承Thread类的方式
class SellTicket01 extends Thread {
private static int ticketNum = 100;//让多个线程共享 ticketNum 因为静态属性是共享的
@Override
public void run() {
while (true) {
//只有当剩余的票小于0才会退出
if (ticketNum <= 0) {
System.out.println("售票结束...");
break;
}
//休眠50毫秒, 模拟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
//输出提示信息,并且每输出一次对总票数进行减一
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
+ " 剩余票数=" + (--ticketNum));
}
}
}
//实现接口方式
class SellTicket02 implements Runnable {
private int ticketNum = 100;//让多个线程共享 ticketNum
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
System.out.println("售票结束...");
break;
}
//休眠50毫秒, 模拟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
+ " 剩余票数=" + (--ticketNum));//1 - 0 - -1 - -2
}
}
}
线程终止
基本说明
1.当线程完成任务后,会自动退出
2.还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式
应用案例 通知线程退出
需求: 启动一个线程t,要求在main线程中去停止线程t,请编程实现
代码演示:
所谓的线程终止,也就是在之前的代码上,做出一点修改,之前我们while循环都是无限循环,然后设置了一个变量,当这个变量达到了一定的值之后,我们就break跳出循环,但是这种方式的话,需要把我们执行的条件,全部执行完毕,然后才会截至,
那么我们想要提前终止这个线程的话,我们需要设置一个布尔值的变量,然后初始化为true,提供set方法,在我们需要中止这个线程的时候,就把该变量设置成false就可以了
package idea.chapter17.exit_;
//求: 启动一个线程t,要求在main线程中去停止线程t,请编程实现
public class ThreadExit_ {
public static void main(String[] args) throws InterruptedException {
T t1 = new T();
t1.start();
//如果希望main线程去控制t1 线程的终止, 必须可以修改 loop
//让t1 退出run方法,从而终止 t1线程 -> 通知方式
//让主线程休眠 10 秒,再通知 t1线程退出
System.out.println("main线程休眠10s...");
Thread.sleep(10 * 1000);
t1.setLoop(false);
}
}
class T extends Thread {
private int count = 0;
//设置一个控制变量
private boolean loop = true;
@Override
public void run() {
while (loop) {
try {
Thread.sleep(50);// 让当前线程休眠50ms
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T 运行中...." + (++count));
}
}
public void setLoop(boolean loop) {
this.loop = loop;
}
}
线程常用方法
常用方法第一组
1.setName //设置线程冬称,使之与参数 name 相同
2.qetName //返回该线程的名称·
3.start //使该线程开始执行; Java 虚拟机底层调用该线程的 start0 方法
4.run //调用线程对象 run 方法
5.setPriority //更改线程的优先级
6.getPriority //获取线程的优先级
7sleep//在指定的毫秒数内让当前正在执行的线程休眠 (暂停执行)
8.interrupt //中断线程
线程方法注意事项和细节
1.start 底层会创建新的线程,调用run,run 就是一个简单的方法调用,不会启动新线程
2线程优先级的范围
3.interrupt,中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠线程
4.sleep:线程的静态方法,使当前线程休眠
代码演示:
//注意interrupt是中段线程,不是退出线程,就是把我们正在休眠的线程,唤醒,唤醒之后线程就会抛出一个这样的异常InterruptedException
package idea.chapter17.method;
/**
* 演示线程的方法
*/
public class ThreadMethod01 {
public static void main(String[] args) throws InterruptedException {
//测试相关的方法
T t = new T();
t.setName("jack");
t.setPriority(Thread.MIN_PRIORITY);//1
t.start();//启动子线程
//主线程打印5 hi ,然后我就中断 子线程的休眠
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println("hi " + i);
}
System.out.println(t.getName() + " 线程的优先级 =" + t.getPriority());//1
//注意interrupt是中段线程,不是退出线程,就是把我们正在休眠的线程,唤醒,唤醒之后线程就会抛出一个这样的异常InterruptedException
t.interrupt();//当执行到这里,就会中断 t线程的休眠.
}
}
class T extends Thread { //自定义的线程类
@Override
public void run() {
while (true) {
for (int i = 0; i < 100; i++) {
//Thread.currentThread().getName() 获取当前线程的名称
System.out.println(Thread.currentThread().getName() + " 吃包子~~~~" + i);
}
try {
System.out.println(Thread.currentThread().getName() + " 休眠中~~~");
Thread.sleep(20000);//20秒
} catch (InterruptedException e) {
//当该线程执行到一个interrupt 方法时,就会catch 一个 异常, 可以加入自己的业务代码
//InterruptedException 是捕获到一个中断异常.
System.out.println(Thread.currentThread().getName() + "被 interrupt了");
}
}
}
}
常用方法第二组
1.yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
2.join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务
代码演示:
//注意:这里使用yield表示线程的礼让,但是不一定礼让成功,并不是固定的,但是如果是join线程插队,比如我们在main方法中调用了t2.join() //那么main线程就会等待子线程也就是t2这个线程执行完毕在继续执行,主线程会堵塞在这里
package idea.chapter17.method;
public class ThreadMethod02 {
public static void main(String[] args) throws InterruptedException {
T2 t2 = new T2();
t2.start();
for(int i = 1; i <= 20; i++) {
Thread.sleep(1000);
System.out.println("主线程 吃了 " + i + " 包子");
if(i == 5) {
System.out.println("主线程 让 子线程 先吃");
//1.yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
//2.join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务
//注意:这里使用yield表示线程的礼让,但是不一定礼让成功,并不是固定的,但是如果是join线程插队,比如我们在main方法中调用了t2.join()
//那么main线程就会等待子线程也就是t2这个线程执行完毕在继续执行,主线程会堵塞在这里
//join, 线程插队
//t2.join();// 这里相当于让t2 线程先执行完毕
Thread.yield();//礼让,不一定成功..
System.out.println("线程 吃完了 主线程 接着吃..");
}
}
}
}
class T2 extends Thread {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
try {
Thread.sleep(1000);//休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程 吃了 " + i + " 包子");
}
}
}
线程课堂练习
代码演示:
思路分析:
1.先在主线程中输出hi 当主线程输出了五句hi之后,我们就马上启动子线程,注意题目要求要我们子线程全部执行完毕,才让主线程继续执行
那么我们刚刚学的两个方法 一个是yield 方法 还有一个是join方法 这两个方法我们需要使用join方法,因为yield不一定会礼让成功
如果使用yield方法可能导致发生主线程和子线程互相交替的执行,形成了一个并发的效果,这样就不满足我们的要求,所以需要使用join方法
就不会导致这种情况发生了,因为join方法是线程插队,当调用了这个方法,就会优先让这个线程执行完毕,才继续执行
package idea.chapter17.method;
/*
1.主线程每隔1s,输出 hi,一共 10次
2.当输出到 hi 5 时,启动一个子线程(要求实现Runnable),每隔1s 输出 hello,等该线程输出10次 hello后,退出
3.主线程继续输出 hi,直到主线程退出
4.如图,完成代码
*/
public class ThreadMethodExercise {
public static void main(String[] args) throws InterruptedException {
T3 t3 = new T3();
Thread thread = new Thread(t3);
for (int i = 0; i < 10; i++) {
System.out.println("hi");
Thread.sleep(1000);
if (i == 5) {
thread.start();
thread.join();
}
}
System.out.println("主线程结束");
}
}
/*
思路分析:
1.先在主线程中输出hi 当主线程输出了五句hi之后,我们就马上启动子线程,注意题目要求要我们子线程全部执行完毕,才让主线程继续执行
那么我们刚刚学的两个方法 一个是yield 方法 还有一个是join方法 这两个方法我们需要使用join方法,因为yield不一定会礼让成功
如果使用yield方法可能导致发生主线程和子线程互相交替的执行,形成了一个并发的效果,这样就不满足我们的要求,所以需要使用join方法
就不会导致这种情况发生了,因为join方法是线程插队,当调用了这个方法,就会优先让这个线程执行完毕,才继续执行
*/
class T3 implements Runnable {
@Override
public void run() {
int count = 0;
while (true) {
try {
System.out.println("hello" + (++count));
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10) {
System.out.println("子线程结束");
break;
}
}
}
}
用户线程和守护线程
1.用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
2.守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束3.常见的守护线程:垃圾回收机制
代码演示:
思路分析:
当我们有这样的一个需求,就是当主线程执行完毕退出之后,子线程也跟着退出,那么在正常的情况下,是不会退出的
即使主线程已经,已经销毁了,但是子线程并不会结束,还会继续的执行,那么如果想要主线程结束,子线程也跟着结束
那么就需要讲子线程设置为守护线程就可以,默认情况下我们的子线程是无线循环,一直不会退出,只需要这样设置一些myDaemonThread.setDaemon(true);
那么子线程就是守护线程,注意要先设置为守护线程在启动
package idea.chapter17.method;
public class ThreadMethod03 {
public static void main(String[] args) throws InterruptedException {
MyDaemonThread myDaemonThread = new MyDaemonThread();
//如果我们希望当main线程结束后,子线程自动结束
//,只需将子线程设为守护线程即可
myDaemonThread.setDaemon(true);
myDaemonThread.start();
for (int i = 1; i <= 10; i++) {//main线程
System.out.println("mary在辛苦的工作...");
Thread.sleep(1000);
}
}
}
/*
思路分析:
当我们有这样的一个需求,就是当主线程执行完毕退出之后,子线程也跟着退出,那么在正常的情况下,是不会退出的
即使主线程已经,已经销毁了,但是子线程并不会结束,还会继续的执行,那么如果想要主线程结束,子线程也跟着结束
那么就需要讲子线程设置为守护线程就可以,默认情况下我们的子线程是无线循环,一直不会退出,只需要这样设置一些myDaemonThread.setDaemon(true);
那么子线程就是守护线程,注意要先设置为守护线程在启动
*/
class MyDaemonThread extends Thread {
public void run() {
for (; ; ) {//无限循环
try {
Thread.sleep(1000);//休眠1000毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("jack 和 tom 一直在聊天~~~");
}
}
}