Java多线程基础
多线程基础相关笔记
1.线程相关知识
1.1、程序
是为完成特定任务、用某种语言编写的一组指令的集合。
简单的说:就是我们写的代码
1.2、进程
1.进程是指运行中的程序,比如我们使用浏览器,就启动了一个进程,操作系统就会为
该进程分配内存空间。
2.进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的
产生、存在和消亡的过程.
1.2、线程
1.线程由进程创建的,是进程的一个实体。
2.一个进程可以拥有多个线程。
1.3、并发与并行
并发是指在一个时间段内有多个进程在执行。
并行指的是在同一时刻有多个进程在同时执行。
如果是在只有一个CPU的情况下,是无法实现并行的,因为同一时刻只能有一个进程被调度执行,如果此时同时要执行其他进程则必须上下文切换,这种只能称之为并发,而如果是多个CPU的情况下,就可以同时调度多个进程,这种就可以称之为并行。(一般情况下为多核)
举个例子,一个人坐在车上同时打电话和开车为并发,两个人坐在车上一个人打电话一个人开车分工进行为并行。
2.线程的基本使用
2.1、案例1:继承Thread类,重写run方法
业务逻辑:每隔一秒输出“喵喵,我是小猫咪“,输出多少次。
public class MyThread {
public static void main(String[] args) {
//创建一个cat对象可以当做线程使用
Cat cat=new Cat();
cat.start();//启动线程==>最终会执行 cat 的 run 方法
/**
* cat.run();run 方法就是一个普通的方法, 没有真正的启动一个线程,就会把 run 方法执行完毕,才向下执行
* 调用 cat.run()打印的线程名称是main(可以观看打印结果)
* 说明: 当 main 线程启动一个子线程 Thread-0, 主线程不会阻塞, 会继续执行
* 这时 主线程和子线程是交替执行..
*/
for(int i = 0; i < 80; i++){
System.out.println("主线程 i=" + i+"主线程继续执行" + Thread.currentThread().getName()); //让主线程main休眠
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 1:当一个类继承了Thread类,该类就可以当做线程使用
* 2:我们会重写run方法, 写上自己的业务代码
* 3:run Thread 类实现了RunnabLe 接口的run方法
*/
class Cat extends Thread{
//业务逻辑:每隔一秒输出“喵喵,我是小猫咪“
int num=0;
@Override
public void run(){//重写run方法,写上自己的业务逻辑
while(true){
System.out.println("喵喵,我是小猫咪:"+(++num)+""+":cat线程名称" + Thread.currentThread().getName());
//快捷键Ctrl+Alt+t
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (num==80){
break;
}
}
}
}
2.2、案例2:通过实现接口Runnable来开发线程
业务逻辑:请编写程序该程序可以每隔1秒。在控制台输出"小狗汪汪叫" ,当输出10次后,自动退出。
这里底层使用了设计模式[代理模式] =>代码模拟实现Runnable接口开发线程的机制。下面代码会模拟代理(可以打断点观察运行过程)。
public class MyThread02 {
public static void main(String[] args) {
Dog dog=new Dog();
//dog.start();这里不能调用start();
// 创建Thread 对象,把 dog 对象(实现 Runnable),放入 Thread
Thread thread=new Thread(dog);
thread.start();
Tiger tiger = new Tiger();//实现了 Runnable
ThreadProxy threadProxy = new ThreadProxy(tiger);
threadProxy.start();
}
}
class Dog implements Runnable{//通过实现接口Runnable来开发线程
int num=0;
@Override
public void run() {
while (true){
System.out.println("小狗汪汪叫:"+(++num)+""+"。线程名称:" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (num==10){
break;
}
}
}
}
class Animal { }
class Tiger extends Animal implements Runnable {
@Override
public void run(){
System.out.println("老虎嗷嗷叫....");
}
}
//线程代理类 , 模拟了一个极简的 Thread 类
class ThreadProxy implements Runnable {//线程代理
private Runnable target = null;//属性,类型是 Runnable
@Override
public void run() {
if (target != null) {
target.run();//动态绑定(运行类型 Tiger) } }
}
}
public ThreadProxy(Runnable target) {
this.target = target;
}
public void start() {
start0();//这个方法时真正实现多线程方法
}
public void start0() {
run();
}
}
2.3、案例3:多线程执行
public class MyThread03 {
public static void main(String[] args) {
T1 t1=new T1();
T2 t2=new T2();
Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t2);
thread1.start();
thread2.start();
}
}
class T1 implements Runnable{
int num=0;
@Override
public void run() {
while (true){
System.out.println("小狗汪汪汪汪~:"+(++num)+""+"。线程名称:" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (num==60){
break;
}
}
}
}
class T2 implements Runnable{
int num=0;
@Override
public void run() {
while (true){
System.out.println("小鸟渣渣渣~:"+(++num)+""+"。线程名称:" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (num==60){
break;
}
}
}
}
2.4、继承 Thread vs 实现 Runnable 的区别
从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本
质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现Runnable接口。
实现Runnable接口方式更加适合多个线程共享一个资源的情况, 并且避免了单继承的限制,建议使用Runnable。
编写购票系统留下问题:在下面的购票系统中(两种创建线程的方法都会出现问题)会有一张票被售出多次和超卖的现象。(区别在后面的第6.2章节体现的特别明显,仔细研究6.2章节代码与下面代码可以看出区别)
public class MyThread04 {
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 sellTicket04= new SellTicket02();
// new Thread(sellTicket04).start();//第 1 个线程-窗口
// new Thread(sellTicket04).start();//第 2 个线程-窗口
// new Thread(sellTicket04).start();//第 3 个线程-窗口
}
}
class SellTicket01 extends Thread {
private static 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));
}
}
}
//实现接口方式
class SellTicket02 implements Runnable{
private static 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));
}
}
}
3.线程的退出和终止
1.当线程完成任务后,会自动退出。
2.还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式。
通知方式演示:
package com.wushang.threadStudy;
public class MyThread05 {
public static void main(String[] args) {
ThreadTest threadTest=new ThreadTest();
Thread thread=new Thread(threadTest);
thread.start();
//改变Lifestate的值使其结束(通知)
try {
//为了效果明显让thread这个线程张扬一会儿(main线程休眠)时间自定义
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//主线程停止休眠之后更改Lifestate的值通知它寿命到了。
threadTest.exLifestatus(false);
}
}
class ThreadTest implements Runnable{
private Boolean Lifestate=true;
private int num=0;
@Override
public void run() {
while (Lifestate){
System.out.println("你被狗咬了"+(++num)+"口,请赶紧去打狂犬疫苗");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void exLifestatus(Boolean status){
this.Lifestate=status;
//用于接收阎王(main线程)的通知。
}
}
4.线程的常用方法
4.1、第一组:
1. setName //设置线程名称,使之与参数name相同
2. getName //返回该线程的名称
3. start //使该线程开始执行; Java 虚拟机底层调用该线程的startO方法
4. run //调用线程对象run方法:
5. setPriority //更改线程的优先级
6. getPriority //获取线程的优先级
7. sleep
//在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
8. interrupt //中断线程
/**
1. start 底层会创建新的线程,调用run, run就是一个简单的方法调用,不会启动新
线程
2.线程优先级的范围
3. interrupt,中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠线程
4. sleep:线程的静态方法,使当前线程休眠
*/
应用举例,(张三搬砖(老板和张三属于一个进程))
package com.wushang.threadStudy;
public class MyThread06 {
public static void main(String[] args) {
T t=new T();
Thread thread=new Thread(t);
thread.setName("张三");
thread.setPriority(1);
thread.start();
for (int i=5;i>0;i--){
try {
Thread.sleep(1000);
System.out.println("老板来了在喊倒计时"+i+","+thread.getName()+"该起来工作了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("倒计时结束执行thread.interrupt()");
thread.interrupt();//中断张三的休眠状态
//中断执行之后main线程执行结束(老板走了,张三搬10块砖之后又开始偷懒)
}
}
class T implements Runnable{
@Override
public void run() {
while (true){
// System.out.println();
for (int i=1;i<=10;i++){
System.out.println(Thread.currentThread().getName()+"为了挣钱养家搬了"+i+"块砖");
}
try {
System.out.println(Thread.currentThread().getName()+"快要累死了休息会儿,然后准备继续搬砖");
Thread.sleep(20000);
} catch (InterruptedException e) {
//当休眠被终端的时候会抛出一个异常
System.out.println(Thread.currentThread().getName()+"被老板中断休眠(interrupt),赶紧起来搬砖");
}
}
}
}
4.3、第二组:
1. yield//线程的礼让。让出cpu, 让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
2. join//线程的插队。插队的线程- 旦插队成功,则肯定先执行完插入的线程所有的任务
//案例:(体育老师和校长亲信赛跑)
public class MyThread07 {
public static void main(String[] args) {
System.out.println("体育老师举办了运动会,和校长亲信比赛跑步");
Th th=new Th();
Thread t=new Thread(th);
t.start();
for (int i=100;i>=0;i--){
try {
Thread.sleep(500);
System.out.println("体育老师(main)还差"+i+"m到达终点");
if(i==10){
System.out.println("体育老师面对人情世故,在距离终点线10米的地方崴脚(join())了,让校长亲信跑到终点时站起来继续跑");
t.join();
//System.out.println("体育老师礼让一下校长亲信");
// Thread.yield();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Th implements Runnable{
@Override
public void run() {
for (int i=100;i>=0;i--){
try {
Thread.sleep(500);
System.out.println("校长亲信还差"+i+"m到达终点");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
4.3用户线程和守护线程
1.用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
2.守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
3.常见的守护线程:垃圾回收机制
案例:小强和小王监控老板位置然后打游戏。
package com.wushang.threadStudy;
public class MyThread08 {
public static void main(String[] args) {
Thr thr=new Thr();
Thread t=new Thread(thr);
//如果我们希望当main线程结束后,子线程自动结束
//简单理解老板没有在办公室工作的时候小强和小王停止打游戏
//只需将子线程设为守护线程即可(Start之前)
t.setDaemon(true);
t.start();
for (int i=0;i<10;i++) {
try {
Thread.sleep(500);
System.out.println("老板在办公室工作,");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Thr implements Runnable{
@Override
public void run() {
while (true){//无线循环
try {
Thread.sleep(500);
System.out.println("小强和小王上班时候打游戏");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
5.线程的生命周期
public static enum Thread. State
extends Enum<Thread 。State>
/*
线程状态。线程可以处于以下状态之一 :
●NEW
尚未启动的线程处于此状态。
●RUNNABIE(可运行状态)
在Java虛拟机中执行的线程处于此状态。
//@TODO RUNNABIE分为两个状态,running(运行态)和Ready(就绪状态)状态
●BLOCKED
被阻塞等待监视器锁定的线程处于此状态。
●WAITING
正在等待另一个线程执行特定动作的线程处于此状态。
●TIMED WAITING
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
●TERMINATED
己退出的线程处于此状态。
*/
/**
对流程图的简单解释(关于其中的区别看百度)
yield()==>礼让
new==>创建
start()==>执行
join()==>插队
sleep()==>休眠
/*
wait()==>等待
notify()==>当前的线程已经放弃对资源的占有,通知等待的线程来获得对资源的占有权,但是只有一个线程能够wait状态中恢复,然后继续运行wait()后面的语句;只会唤醒等待该锁的其中一个线程。
notifyAll()==>当前的线程已经放弃对资源的占有,通知所有的等待线程从wait()方法后的语句开始运行;唤醒等待该锁的所有线程。
*/
/*
其实park/unpark的设计原理核心是“许可”:park是等待一个许可,unpark是为某线程提供一个许可。 LockSupport.park(thread)==>中断
LockSupport.unpark(thread)==>唤醒
*/
*/
6、线程的同步机制
6.1、理论基础和注意事项
/*
1.在多线程编程,-些敏感数据不允许被多个线程同时访问,此时就使用同步访问技
术,保证数据在任何同时刻, 最多有一个线程访问,以保证数据的完整性。
2. 也可以这里理解: 线程同步,即当有一个线程在对内存进行操作时, 其他线程都不
可以对这个内存地址进行操作,直到该线程完成操作,其他线程才 能对该内存地
址进行操作
*/
/*1. Java语言中, 引入了对象互斥锁的概念,来保证共享数据操作的完整性。
2.每个对象都对应一个可称为“互斥锁"的标记,这个标记用来保证在任一时刻,只
能有一个线程访问该对象。
3.关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,
表明该对象在任一时刻只能由一个线程访问
4.同步的局限性:导致程序的执行效率要降低
5.同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个锁对象)
6.同步方法(静态的)的锁为当前类本身。
*/
6.2、案例介绍(本章精华)
在前面2.4章节的购票系统中埋下了雷,现在予以解决。此时可以很明显的看出来继承和接口的区别。
package com.wushang.threadStudy;
public class MyThread04 {
public static void main(String[] args) {
// System.out.println("===使用实现继承方式来售票(同步方法+继承)=====");
// SellTicket01 s10 = new SellTicket01();
// SellTicket01 s11 = new SellTicket01();
// SellTicket01 s12 = new SellTicket01();
// //这里我们会出现超卖..
// s10.start();//启动售票线程
// s11.start();//启动售票线程
// s12.start();//启动售票线程
//
// System.out.println("===使用实现接口方式来售票(接口+同步方法)=====");
// SellTicket02 s2= new SellTicket02();
// new Thread(s2).start();//第 1 个线程-窗口
// new Thread(s2).start();//第 2 个线程-窗口
// new Thread(s2).start();//第 3 个线程-窗口
//
// System.out.println("===使用实现接口方式来售票(接口+同步代码块测试)=====");
// SellTicket03 s3= new SellTicket03();
// new Thread(s3).start();//第 1 个线程-窗口
// new Thread(s3).start();//第 2 个线程-窗口
// new Thread(s3).start();//第 3 个线程-窗口
System.out.println("===使用实现继承方式来售票(同步代码快+继承)=====");
SellTicket04 s40 = new SellTicket04();
SellTicket04 s41 = new SellTicket04();
SellTicket04 s42 = new SellTicket04();
//这里我们会出现超卖..
s40.start();//启动售票线程
s41.start();//启动售票线程
s42.start();//启动售票线程
}
}
//extends Thread直接用同步方法会超卖,是因为每个线程都是执行他们各自对象的方法
//假如使用继承Thread类的话,会创建多个对象,所以继承thread 类多线程得加类锁 即 static synchronized,在后面代码块时不能直接用this也是这个道理,锁对象应该是同一个
class SellTicket01 extends Thread {
private static int ticketNum = 100;//让多个线程共享 ticketNum
private static Boolean loop=true;
public static synchronized void sell(){//同步方法+继承
if (ticketNum <= 0) {
System.out.println("售票结束...");
loop=false;
return;
}//休眠 50 毫秒, 模拟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数=" + (--ticketNum));
}
@Override
public void run(){
while (loop) {
sell();
}
}
}
//实现接口方式
class SellTicket02 implements Runnable{
private int ticketNum = 100;//让多个线程共享 ticketNum
private Boolean loop=true;
public synchronized void sell(){
if (ticketNum <= 0) {
System.out.println("售票结束了...");
loop=false;
return;
}//休眠 50 毫秒, 模拟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数=" + (--ticketNum));
}
@Override
public void run(){
while (loop) {
sell();
}
}
}
class SellTicket03 implements Runnable{
private int ticketNum = 100;//让多个线程共享 ticketNum
private Boolean loop=true;
public /**synchronized*/ void sell(){
synchronized (this) {
if (ticketNum <= 0) {
System.out.println("售票结束了...");
loop = false;
return;
}
try {
//休眠 50 毫秒, 模拟
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数=" + (--ticketNum));
}
}
@Override
public void run(){
while (loop) {
sell();
}
}
}
class SellTicket04 extends Thread {
private static int ticketNum = 100;//让多个线程共享 ticketNum
private static Boolean loop=true;
private static Object object=new Object();
//这里使用的是继承,在main中new了3个对象,所以要用一个锁对象来表示。
public static /**synchronized*/ void sell() {
synchronized (object) {
if (ticketNum <= 0) {
System.out.println("售票结束...");
loop = false;
return;
}//休眠 50 毫秒, 模拟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数=" + (--ticketNum));
}
}
@Override
public void run(){
while (loop) {
sell();
}
}
}
7、线程死锁
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生。
生活举例:甲和乙打架时揪住了对方的头发,甲说:“你放手,然后我再放手”。乙说:“你先放手,然后我再放手”,一直循环(不要模仿他们打架,危险动作)
public class MyThread09 {
public static void main(String[] args) {
//模拟死锁现象
DeadLockDemo A = new DeadLockDemo(true);
A.setName("A 线程");
DeadLockDemo B = new DeadLockDemo(false);
B.setName("B 线程");
A.start();
B.start();
}
}
class DeadLockDemo extends Thread {
static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用 static
static Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag) {//构造器
this.flag = flag;
}
@Override
public void run() {
//下面业务逻辑的分析
// 1. 如果 flag 为 True, 线程 A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁
// 2. 如果线程 A 得不到 o2 对象锁,就会 Blocked(阻塞)
// 3. 如果 flag 为 False, 线程 B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁
// 4. 如果线程 B 得不到 o1 对象锁,就会 Blocked(阻塞)
if (flag) {
synchronized (o1) {//对象互斥锁, 下面就是同步代码
System.out.println(Thread.currentThread().getName() + " 进入 1");
synchronized (o2) { // 这里获得 li 对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入 2");
}
}
} else {
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " 进入 3");
synchronized (o1) { // 这里获得 li 对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入 4");
}
}
}
}
}
8.释放锁
释放锁的操作:
1.当前线程的同步方法、同步代码块执行结束
案例:上厕所,完事出来
2.当前线程在同步代码块、同步方法中遇到break、return.
案例:没有正常的完事,经理叫他修改bug,不得已出来
3.当前线程在同步代码块、同步方法中出现了未处理的Error或Exception, 导致异常结束
案例:没有正常的完事,发现忘带纸,不得已出来
4.当前线程在同步代码块、同步方法中执行了线程对象的wait(方法,当前线程暂停,井释
放锁。
案例:没有正常完事,觉得需要酝酿下,所以出来等会再进去
不会释放锁的操作:
1.线程执行同步代码块或同步方法时,程序调用Thread.sleep(). Thread.yield0方
法暂停当前线程的执行,不会释放锁
案例上厕所,太困了,在坑位上眯了一会
2.线程执行同步代码块时,其他线程调用了该线程的suspend0方法将该线程挂起,
该线程不会释放锁。
提示:应尽量避免使用suspend0和resume()来控制线程,方法不再推荐使用
(sleep原地休息,不会将资源交出去,wait将资源交出去然后等待)
9.应用举例
9.1、例1
//(1)在main方法中启动两个线程(线程协调问题)
//(2)第1个线程循环随机打印100以内的整数
//(3)直到第2个线程从键盘读取了"Q"命令。
思考:
我看到网上有些人说使用守护线程,但是俺觉得要考虑如果还有其他线程或者main里面还有任务呢是否第二个线程还可以控制第一个线程呢?
//(1)在main方法中启动两个线程
//(2)第1个线程循环随机打印100以内的整数
//(3)直到第2个线程从键盘读取了"Q"命令。
import java.util.Scanner;
public class MyThread10 {
public static void main(String[] args) {
Th1 th1=new Th1();
Thread thread=new Thread(th1);
// thread.setDaemon(true);
// 考虑一个问题如果main线程还有任务那么会有什么效果
thread.start();
Th2 th2=new Th2(th1);
Thread thread1=new Thread(th2);
thread1.start();
// Th3 th3=new Th3();
// Thread thread1=new Thread(th3);
// thread1.start();
}
}
class Th1 implements Runnable{
private Boolean flag=true;
@Override
public void run() {
while (flag){
try {
int ranNum=(int) (Math.random()*(100-1))+1;
System.out.println("100以内的随机数:"+ranNum);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void setFlag(Boolean flag){
this.flag=flag;
}
}
class Th2 implements Runnable{
//方法1:直接改变Th1里面的数据使其停止循环打印
private Th1 th1;
private Boolean loop=true;
private Scanner input = new Scanner(System.in);
public Th2(Th1 th1) {
this.th1 = th1;
}
@Override
public void run() {
while (loop){
System.out.println("请输入Q,停止线程th1:");
char key = input.next().toUpperCase().charAt(0);
if(key=='Q'){
th1.setFlag(false);
System.out.println("线程停止。。。。。");
loop=false;
}
}
}
}
class Th3 implements Runnable{
//通过守护线程然后控制线程th1,守护线程是等所有的结束之后才会停
//(考虑一个问题如果main线程还有任务那么会有什么效果,所以要考虑周全)
//方法1:直接改变Th1里面的数据使其停止循环打印
private Boolean loop=true;
private Scanner input = new Scanner(System.in);
@Override
public void run() {
while (loop){
System.out.println("请输入Q,停止线程th1:");
char key = input.next().toUpperCase().charAt(0);
if(key=='Q'){
System.out.println("线程停止。。。。。");
loop=false;
}
}
}
}
9.2、例2
/*
(1)有2个用户分别从同一个卡上取钱(总额: 10000)
(2)每次都取1000,当余额不足时,就不能取款了
(3)不能出现超取现象=》线程同步问题
*/
public class MyThread11 {
public static void main(String[] args) {
Th01 th01=new Th01();
Thread thread1=new Thread(th01);
thread1.setName("甲");
thread1.start();
Thread thread2=new Thread(th01);
thread2.setName("乙");
thread2.start();
Thread thread3=new Thread(th01);
thread3.setName("丙");
thread3.start();
}
}
class Th01 implements Runnable{
private Boolean flag=true;
private int money=100000;
public void quqian(){
synchronized (this){
try {
if(money < 1000){
System.out.println("余额不足..");
flag=false;
return;//要结束这个方法
}
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
money-=1000;
System.out.println(Thread.currentThread().getName()+"取1000元钱,银行还剩"+money+"元");
}
}
@Override
public void run() {
while (flag){
quqian();
}
}
}
看韩顺平老师在哔哩哔哩线程专题教学视频写的笔记,
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?