当前位置: 首页 > news >正文

【JAVA学习笔记】61 - 线程入门、常用方法、同步机制,以及本章作业(难点)

项目代码

https://github.com/yinhai1114/Java_Learning_Code/tree/main/IDEA_Chapter17/src/com/yinhai

线程

一、线程相关概念

1.程序

是为完成特定任务、用某种语言编写的一组指令的集合。简单的说:就是我们写的代码
 

2.进程

        1)进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。

        2)进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程

3.线程

        1)线程由进程创建的,是进程的一个实体

        2)一个进程可以拥有多个线程, 如下图

        3)并发:同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单的说,单核cpu实现的多任务就是并发。

        4)并行:同个时刻,多个任务同时执行。多核cpu可以实现并行。并发和并行可能都会同时存在

java查看cpu核数

public class CpuNum {public static void main(String[] args) {Runtime runtime = Runtime.getRuntime();int cpuNum = runtime.availableProcessors();System.out.println(cpuNum);}
}

二、线程基本使用

1.创建线程的两个方式

在java中线程来使用有两种方法。

        1)继承Thread类,重写run方法

        2)实现Runnable接口,重写run方法

        

2.Thread类使用案例

1)请编写程序,开启一个线程,该线程每隔1秒。在控制台输出"喵喵, 我是小猫咪".

2)对上题改进:当输出8次喵喵,我是小猫咪,结束该线程

3)使用JConsole监控线程执行情况,并画出程序示意图

        

当主线程结束了,可能还有其他的子线程,进程可能并没有结束,会跑完子线程,直到子线程也挂掉了,进程就挂掉了

public class Thread01 {public static void main(String[] args) {//创建一个Cat对象当做线程使用Cat cat = new Cat();cat.start();//当main线程启动一个子线程 Thread-0主线程不会阻塞,会继续执行//这时我们的主线程和Thread 0线程都在执行for (int i = 1; i <= 10; i++) {System.out.println("主线程在执行中" + i + " " + Thread.currentThread().getName());try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
//当一个类继承了Thread类,该类就可以当做线程使用
class Cat extends Thread{int time = 0;@Overridepublic void run() {//往往要重写run方法,Thread类实现了Runnable的接口的run方法while (true){System.out.println("喵喵" + ++time + Thread.currentThread().getName());try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}if(time == 80){break;//当time到8次,线程就会退出}}}
}


补充,为什么使用cat.start,再由该方法调用run呢,而不是使用cat.run呢,很好理解,因为如果直接调用run方法,主线程并不会并列执行该方法,而是由主线程main去调用run方法,会导致没有实现多线程,也就是阻塞下面的语句,这种调用不叫多线程

3.Thread类源码


 

可以看到 这里的start0是本地方法由JVM调用的,而我们重写了该方法的内容,所以准确来说是JVM实现了多线程而不是run方法实现多线程

4.Runnable接口

1. java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法创建线程显然不可能了。

2. java设计者们提供了另外一个方式创建线程,就是通过实现Runnable接口来创建线程,实际上底层使用了设计模式[代理模式] => 代码模拟

public class Thread02 {public static void main(String[] args) {Dog dog = new Dog();//dog.start(); 不能直接调用start方法,该接口只有run方法Thread thread = new Thread(dog);//创建一个Thread对象,把实现了dog对象放入Threadthread.start();// Tiger tiger = new Tiger();//实现了 Runnable// ThreadProxy threadProxy = new ThreadProxy(tiger);// threadProxy.start();}
}class Dog implements Runnable{//实现Runnable接口开发线程int time = 0;@Overridepublic void run() {while (true) {System.out.println("汪汪" + ++time + Thread.currentThread().getName());try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}if (time == 10){break;}}}
}

3.模拟设计代理模式

        可以简单理解为,两个类一个ThreadProxy和Tiger都实现了Runnable类,在构造器的时候传入tiger到代理对象内,此时使用代理对象的start方法就可以动态绑定到我们传入的targer的run方法

public class Thread02 {public static void main(String[] args) {Tiger tiger = new Tiger();//实现了 RunnableThreadProxy threadProxy = new ThreadProxy(tiger);threadProxy.start();}
}
//线程代理类 , 模拟了一个极简的Thread类
class ThreadProxy implements Runnable {//你可以把Proxy类当做 ThreadProxyprivate Runnable target = null;//属性,类型是 Runnable@Overridepublic void run() {if (target != null) {target.run();//动态绑定(运行类型Tiger)}}public ThreadProxy(Runnable target) {this.target = target;}public void start() {start0();//这个方法时真正实现多线程方法}public void start0() {run();}
}
class Animal {
}class Tiger extends Animal implements Runnable {@Overridepublic void run() {System.out.println("老虎嗷嗷叫....");}
}

5.线程使用的基本案例

请编写一个程序,创建两个线程,一个线程每隔1秒输出"hello,world" ,输出10次,退出,一个线程每隔1秒输出"hi" ,输出5次退出,实现Runnable的方式来完成

public class Thread03 {public static void main(String[] args) {T1 t1 = new T1();T2 t2 = new T2();Thread thread = new Thread(t1);Thread thread1 = new Thread(t2);thread.start();thread1.start();}
}class T1 implements Runnable {@Overridepublic void run() {int i = 0;while (true) {System.out.println("hello,world" + ++i + Thread.currentThread().getName());try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}if (i == 10) {break;}}}
}class T2 implements Runnable {int i = 0;@Overridepublic void run() {while (true) {System.out.println("hi" + ++i + Thread.currentThread().getName());try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}if (i == 5) {break;}}}
}

6.继承Thread 和 实现Runnable的区别

        1) 从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口

        2)实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制

三、模拟三个售票窗口

        继承Thread类创建三个对象去调用strat方法会出现一个很严重的问题,多线程冲突,都跑去--ticketNum去了,会出现来不及减完就会超卖的现象

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();}
}
class SellTicket01 extends Thread{private static int ticketNum = 100;@Overridepublic void run() {while (true){if(ticketNum <= 0){System.out.println("售票结束");break;}try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("该窗口" + Thread.currentThread().getName() + "卖出了一张票,剩余 " + --ticketNum + "张");}}
}

                

        使用Runnable类也会出现上面的情况,实际上两个实现的方式本质都一样

public class SellTicket {public static void main(String[] args) {//使用实现Runnable接口方式SellTicket02 sellTicket002 = new SellTicket02();new Thread(sellTicket002).start();new Thread(sellTicket002).start();new Thread(sellTicket002).start();}
}class SellTicket02 implements Runnable{private int ticketNum = 100;@Overridepublic void run() {while (true){if(ticketNum <= 0){System.out.println("售票结束");break;}try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("该窗口" + Thread.currentThread().getName() + "卖出了一张票,剩余 " + --ticketNum + "张");}}
}

留下一个问题,怎么去解决超卖现象呢?看线程同步机制的具体方法内的改善

四、线程终止(通知方式

        1.当线程完成任务后,会自动退出

        2.还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式

public class ThreadExit_ {public static void main(String[] args) {T t = new T();t.start();System.out.println("主线程休眠5秒");//如果希望主线程去控制t1的线程终止,就需要修改loop变量//让t1退出run方法,从而终止t1线程 - > 通知方式try {//让主线程休眠10秒再退出Thread线程Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}t.setLoop(false);}
}
class T extends Thread{private int count = 0;//设置一个控制变量private boolean loop = true;@Overridepublic 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. getName //返回该线程的名称

3. start //使该线程开始执行; Java 虚拟机底层调用该线程的startO方法

4. run //调用线程对象 run方法;

5. setPriority //更改线程的优先级

6. getPriority /获取线程的优先级

7. sleep//在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)

8. interrupt //中断线程

1.方法注意事项

1. start底层会创建新的线程,调用run, run 就是一个简单的方法调用, 不会启动新线程

2.线程优先级的范围为1-10

/** * The minimum priority that a thread can have. */ 
public final static int MIN_PRIORITY = 1; /** * The default priority that is assigned to a thread. */ 
public final static int NORM_PRIORITY = 5; /** * The maximum priority that a thread can have. */ 
public final static int MAX_PRIORITY = 10;

3. interrupt:中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠线程

        Thread 线程在执行任务时会周期性地检查中断状态。当 Thread.interrupt() 被调用时,线程会在适当的时机检测到中断状态并终止执行。

        Thread.currentThread().isInterrupted();获取当前是否Interrupted

        需要注意的是,interrupt() 方法并不会强制终止线程,而是提供了一种协作的机制,线程可以根据中断状态自行决定是否终止。

        如果线程不检查中断状态,或者没有处理 InterruptedException 异常,它可能会继续执行。

        所以在下列代码中,我们catch了InterruptedException 异常,但并没有退出线程的操作导致程序继续执行,也就是中断了线程的休眠状态

4. sleep:线程的静态方法,使当前线程休眠

public class ThreadMethod01 {public static void main(String[] args) throws InterruptedException {//测试相关的方法T t = new T();t.setName("yinhai");t.setPriority(Thread.MIN_PRIORITY);//1t.start();//启动子线程//主线程打印5 hi ,然后就中断 子线程的休眠for(int i = 1; i <= 5; i++) {Thread.sleep(1000);System.out.println("hi " + i);}System.out.println(t.getName() + " 线程的优先级 =" + t.getPriority());//1t.interrupt();//当执行到这里,就会中断 t线程的休眠.}
}class T extends Thread { //自定义的线程类@Overridepublic 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了");}}}
}

                

第二组(yield、join)

1. yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功。本意上也是让出cpu给其他线程插队

        

2. join:线程的插队。插队的线程一旦插队成功(一定会成功),则肯定先执行完插入的线程所有的任务。

        

案例:创建一个子线程,每隔1s输出hello,输出10 次,主线程每隔1秒,输出hi,输出10次.要求:两个线程同时执行,当主线程输出5次后,就让子线程运行完毕,主线程再继续。

public class ThreadMethod02 {public static void main(String[] args) throws InterruptedException {T2 t2 = new T2();t2.start();for(int i = 1; i <= 10; i++) {Thread.sleep(1000);System.out.println("主线程(小弟) 吃了 " + i  + " 包子");if(i == 5) {System.out.println("主线程(小弟) 让 子线程(老大) 先吃");//join, 线程插队t2.join();// 这里相当于让t2 线程先执行完毕// Thread.yield();//礼让,不一定成功..System.out.println("线程(老大) 吃完了 主线程(小弟) 接着吃..");}}}
}class T2 extends Thread {@Overridepublic void run() {for (int i = 1; i <= 10; i++) {try {Thread.sleep(1000);//休眠1秒} catch (InterruptedException e) {e.printStackTrace();}System.out.println("子线程(老大) 吃了 " + i +  " 包子");}}
}

如果是礼让yield不一定成功

课堂练习

public class ThreadMethodExercise {public static void main(String[] args) throws InterruptedException {Thread t3 = new Thread(new T3());//创建子线程for (int i = 1; i <= 10; i++) {System.out.println("hi " + i);if(i == 5) {//说明主线程输出了5次 hit3.start();//启动子线程 输出 hello...t3.join();//立即将t3子线程,插入到main线程,让t3先执行}Thread.sleep(1000);//输出一次 hi, 让main线程也休眠1s}System.out.println("主线程结束");}
}class T3 implements Runnable {private int count = 0;@Overridepublic void run() {while (true) {System.out.println("hello " + (++count));try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}if (count == 10) {System.out.println("子线程结束");break;}}}
}

第三组(用户线程和守护线程)

1.用户线程:也叫工作线程,以当线程的任务执行完成通知方式来结束

2.守护线程:一般是为工作线程服务的,当所有的用户线程结束守护线程自动结束

3.常见的守护线程:垃圾回收机制

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("宝强在辛苦的工作...");Thread.sleep(1000);}}
}class MyDaemonThread extends Thread {public void run() {for (; ; ) {//无限循环try {Thread.sleep(1000);//休眠1000毫秒} catch (InterruptedException e) {e.printStackTrace();}System.out.println("马蓉和宋喆快乐聊天,哈哈哈~~~");}}
}

六、线程的生命周期

        官方API文档指出有6种状态

七个则是Runnable里的可执行的里的细分状态,Ready和Running状态,取决于调度器选中执行,不是start()后就立马执行,要取决底层调度器是否执行该线程

线程状态图

public class ThreadState_ {public static void main(String[] args) throws InterruptedException {T t = new T();System.out.println(t.getName() + " 状态 " + t.getState());t.start();while (Thread.State.TERMINATED != t.getState()) {System.out.println(t.getName() + " 状态 " + t.getState());Thread.sleep(500);}System.out.println(t.getName() + " 状态 " + t.getState());}
}
class T extends Thread {@Overridepublic void run() {while (true) {for (int i = 0; i < 10; i++) {System.out.println("hi " + i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}break;}}
}

线程同步机制

一、线程同步机制

1.在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。

        

2.也可以这里理解:线程同步,即当有一个线程在对内存进行操作时其他线程都不可以对这个内存地址进行操作直到该线程完成操作其他线程才能对该内存地址进行操作。
 

二、线程同步具体方法

1.Synchronized

        1)同步代码块synchronized (对象) { //得到对象的锁,才能操作同步代码

                //需要被同步代码;

        }

        2) synchronized还可以放在方法声明中, 表示整个方法为同步方法

                public synchronized void m (String name){

                        //需要被同步的代码

                }

        3)如何理解:就好像某小伙伴上厕所前先把门关上(上锁),完事后再出来(解锁),那么其它小伙伴就可在使用厕所了

        4)使用synchronized解决售票问题

public class SellTicket {public static void main(String[] args) {SellTicket02 sellTicket002 = new SellTicket02();new Thread(sellTicket002).start();new Thread(sellTicket002).start();new Thread(sellTicket002).start();}
}
//实现接口方法,使用synchronized实现线程同步
class SellTicket02 implements Runnable{private int ticketNum = 100;public synchronized boolean sellTick(){//同步方法,同一时间只有一个线程来执行sellTick方法if(ticketNum <= 0){System.out.println("售票结束");return false;}try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("该窗口" + Thread.currentThread().getName() + "卖出了一张票,剩余 " + --ticketNum + "张");return true;}@Overridepublic void run() {while (sellTick()){}}
}

注意 这时候运行状态不一定都会平均分配到到每一个线程上,因为是不公平锁,三个都在抢这个锁,谁抢到就是谁的,分析同步原理看下

三、分析同步原理

三个进程去抢这把锁,当有一个抢到之后,进入run方法输出打印车票,然后归还锁到对象上,随后加入Blocked进程状态三个一起抢这把锁,一直重复执行到结束,没抢到的进入Blocked状态,抢到的进入Runnable状态执行后退出

四、互斥锁

1)Java在Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。

2)每个对象都对应于一个可称为"互斥锁”的标记,这个标记用来保证在任一一时刻,只能有一个线程访问该对象。

3)关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问

4)同步的局限性:导致程序的执行效率要降低

5)同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)

6)同步方法(静态的)的锁为当前类本身。

public class SellTicket {public static void main(String[] args) {SellTicket02 sellTicket002 = new SellTicket02();Thread thread1 = new Thread(sellTicket002);Thread thread2 = new Thread(sellTicket002);Thread thread3 = new Thread(sellTicket002);thread1.start();thread2.start();thread3.start();}
}//实现接口方法,使用synchronized实现线程同步
class SellTicket02 implements Runnable {private int ticketNum = 10;Object object = new Object();//6)同步方法(静态的)的锁为当前类本身。//1. public synchronized static void m1() {} 锁是加在 SellTicket03.classpublic synchronized static void m1() {}//2. 如果在静态方法中,要实现一个同步代码块,需要像以下方式处理public static void m2() {synchronized (SellTicket02.class) {System.out.println("m2");}}//5)同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)//同步方法,同一时间只有一个线程来执行sellTick方法public /*synchronized*/ boolean sellTick() {//2.这时锁在this对象,也可以在代码块上写synchronize,同步代码块synchronized (/*this*/ object) {if (ticketNum <= 0) {System.out.println("售票结束");return false;}System.out.println("该窗口" + Thread.currentThread().getName() + "卖出了一张票,剩余 " + --ticketNum + "张");try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}return true;}}@Overridepublic void run() {while (sellTick()) {}}public int getTicketNum() {return ticketNum;}
}

1.注意事项

1.同步方法如果没有使用static修饰:默认锁对象为this

2.如果方法使用static修饰,默认锁对象:当前类.class

3.实现的落地步骤:

        需要先分析上锁的代码

        选择同步代码块或同步方法(如果可以最好用代码块,作用域小)

        要求多个线程的锁对象为同一个即可!

//使用Thread方式
// new SellTicket01().start();
// new SellTicket01().start();
class SellTicket01 extends Thread {private static int ticketNum = 100;//让多个线程共享 ticketNum//这样锁不住,无法保证锁的对象是同一个public void m1() {synchronized (this) {System.out.println("hello");}}
}

五、线程的死锁

1.基本介绍

        多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生.

2.模拟线程死锁

public class DeadLock_ {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();// 保证多线程,共享一个对象,这里使用staticstatic Object o2 = new Object();boolean flag;public DeadLockDemo(boolean flag) {//构造器this.flag = flag;}@Overridepublic void run() {//下面业务逻辑的分析//1. 如果flag 为 T, 线程A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁//2. 如果线程A 得不到 o2 对象锁,就会Blocked//3. 如果flag 为 F, 线程B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁//4. 如果线程B 得不到 o1 对象锁,就会Blockedif (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");}}}}
}

        

线程不是并行的 有可能出现A线程拿完锁走完了才到B线程,也很好的反应了锁的单独拿的性质

        

六、释放锁

1.释放锁的条件

1)当前线程的同步方法、同步代码块执行结束

        案例:上厕所, 完事出来

2)当前线程在同步代码块、同步方法中遇到break、 return。 

        案例:没有正常的完事,经理叫他修改bug,不得已出来

3)当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束

        案例:没有正常的完事,发现忘带纸,不得已出来

4)当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。

        案例:没有正常完事,觉得需要酝酿下,所以出来等会再进去

2.下面的操作不会释放锁

1)线程执行同步代码块或同步方法时,程序调用Thread.sleep()、 Thread.yield()方法暂停当前线程的执行,不会释放锁

        案例:上厕所, 太困了,在坑位上眯了一会

2)线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。

        提示:应尽量避免使用suspend()和resume()来控制线程,这两个方法不再推荐使用

        

本章作业

1.通知方式完成线程终止

(1)在main方法中启动两个线程

(2)第1个线程循环随机打印100以内的整数

(3)直到第2个线程从键盘读取了"Q" 命令。

为什么不用Listener,因为KeyListener 主要是用于监听和处理用户在图形用户界面 (GUI) 中的键盘输入事件。在 GUI 应用程序中,事件驱动编程模型允许程序在用户与应用程序界面交互时响应输入事件。

在控制台应用程序中,使用 KeyListener 不是常见的做法,因为控制台通常不提供直接的键盘事件处理功能,也没有 GUI 元素来接收键盘输入事件。控制台应用程序通常是基于命令行的,它们等待用户输入,然后以文本形式处理用户的命令或数据。

public class Homework01 {public static void main(String[] args) {PrintNum printNum = new PrintNum();new Thread(printNum).start();new Thread(new MyListener(printNum)).start();}
}class PrintNum implements Runnable{private boolean loop = true;//通知方式,需要定义一个变量接受退出指令public void setLoop(boolean loop) {this.loop = loop;}@Overridepublic void run() {while (loop) {synchronized (this) {try {System.out.println((int)(Math.random()*100));//随机数Thread.sleep(2000);} catch (Exception e) {e.printStackTrace();}}}System.out.println("PrintNum进程退出");}
}
class MyListener implements Runnable{private PrintNum printNum;//需要定义一个printNum,面向对象保证我们改的和运行的是同一个对象Scanner myScanner = new Scanner(System.in);public MyListener(PrintNum printNum) {this.printNum = printNum;//构造器传入Print的对象}@Overridepublic void run() {synchronized (this){while (true) {System.out.println("Q退出,等待用户输入...");char a = myScanner.next().charAt(0);if (a == 'Q') {printNum.setLoop(false);System.out.println("Listener进程退出");break;}else{System.out.println("退出失败 输入Q退出");}}}}
}

        

2.线程同步

(1)有2个用户分别从同一个卡上取钱(总额: 10000)

(2)每次都取1000,当余额不足时,就不能取款了

(3)不能出现超取现象 -> 线程同步问题

        

public class Homework02 {public static void main(String[] args){Bank bank = new Bank();Thread thread1 = new Thread(bank);Thread thread2 = new Thread(bank);thread1.start();thread2.start();}
}class Bank implements Runnable {private int bakAccount = 10000;public int getBakAccount() {return bakAccount;}@Overridepublic void run() {//1. 这里使用 synchronized 实现了线程同步//2. 当多个线程执行到这里时,就会去争夺 this对象锁//3. 哪个线程争夺到(获取)this对象锁,就执行 synchronized 代码块, 执行完后,会释放this对象锁//4. 争夺不到this对象锁,就blocked ,准备继续争夺//5. this对象锁是非公平锁.synchronized (this) {while (true) {if (bakAccount <= 0) {System.out.println("取完");break;}System.out.println(Thread.currentThread().getName() + "取出1000余额剩余" + (bakAccount -= 1000));try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}}}
}

如果想要让两个线程近似交替运行,取完--bankAccount后休眠不要使用synchronized包起来即可。锁如果包括休眠的话,会等另一个线程休眠完后一起抢。不包起来就会一个线程开始休眠,另一个线程直接抢锁

总结一下:执行过快,两个thread基本上是同时启动的,所以会出现两个争抢的情况,如果想两个线程不抢,在主线程内加一个sleep即可

thread1.start();

Thread.sleep(200);

thread2.start();

相关文章:

【JAVA学习笔记】61 - 线程入门、常用方法、同步机制,以及本章作业(难点)

项目代码 https://github.com/yinhai1114/Java_Learning_Code/tree/main/IDEA_Chapter17/src/com/yinhai 线程 一、线程相关概念 1.程序 是为完成特定任务、用某种语言编写的一组指令的集合。简单的说:就是我们写的代码 2.进程 1&#xff09;进程是指运行中的程序&#x…...

C#开发的OpenRA游戏之步兵射击(2)

C#开发的OpenRA游戏之步兵射击(2) 前面已经分析士兵射击的整个过程,理解它是怎么样根据武器来创建弹盒,然后加载子弹。现在来分析子弹是怎么伤害到对方的过程。 继续前面的分析,它创建了子弹类Bullet,在这个类里实现爆炸效果和伤害转化。类Bullet也是由它的信息类Bulle…...

基于Pytorch框架的LSTM算法(一)——单维度单步滚动预测(2)

#项目说明&#xff1a; 说明&#xff1a;1time_steps滚动预测代码 y_norm scaler.fit_transform(y.reshape(-1, 1)) y_norm torch.FloatTensor(y_norm).view(-1)# 重新预测 window_size 12 future 12 L len(y)首先对模型进行训练&#xff1b; 然后选择所有数据的后wind…...

安全操作(安卓推流)程序

★ 安全操作项目 项目描述&#xff1a;安全操作项目旨在提高医疗设备的安全性&#xff0c;特别是在医生离开操作屏幕时&#xff0c;以减少非授权人员的误操作风险。为实现这一目标&#xff0c;我们采用多层次的保护措施&#xff0c;包括人脸识别、姿势检测以及二维码识别等技术…...

【STM32】Systick定时器

一、STM32的5种定时器简介 1.独立看门狗&#xff08;IWDG&#xff09; VS 窗口看门狗&#xff08;WWDG&#xff09; 1.独立看门狗&#xff08;IWDG&#xff09; 独立看门狗&#xff1a;当没有到设定时间之前&#xff0c;给它喂了狗&#xff0c;就会回到初始值。 2.窗口看门狗…...

ZooKeeper监控

ZooKeeper Monitor Guide Zookeeper集群进行监控,发现的方案有三种: JMXzookeeper exporterZK Monitor(Since 3.6.0)采用JMX 进行监控,可获取到的指标项不够丰富。Zookeeper Exporter监控可获得的指标项亦不太够丰富。从3.6.0之后,Zookeeper自带的Monitor结合Prometheus、…...

lua # 获取table数组长度

目录 实测结果展示 情况分类 数组开始索引与数组长度 数组元素中间有nil 数组最后的元素为nil...

前端框架Vue学习 ——(七)Vue路由(Vue Router)

文章目录 Vue路由使用场景Vue Router 介绍Vue Router 使用 Vue路由使用场景 使用场景&#xff1a;如下图&#xff0c;点击部门管理的时候显示部门管理的组件&#xff0c;员工管理的时候显示员工管理的组件。 前端路由&#xff1a;指的是 URL 中的 hash(#号)与组件之间的对应关…...

2023-2024-1高级语言程序设计-一维数组

7-1 逆序输出数组元素的值 从键盘输入n个整数存入一维数组中&#xff0c;然后将数组元素的值逆序输出。 输入格式: 第一行输入整数个数n&#xff1b; 第二行输入n 个整数&#xff0c;数据之间以空格隔开。 输出格式: 逆序输出数组元素的值&#xff0c;每个数据之后跟一个空…...

史上最全,从初级测试到高级测试开发面试题汇总,冲击大厂年50w+

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 接口测试面试相关…...

Python基础入门例程42-NP42 公式计算器(运算符)

最近的博文&#xff1a; Python基础入门例程41-NP41 二进制位运算&#xff08;运算符&#xff09;-CSDN博客 Python基础入门例程40-NP40 俱乐部的成员&#xff08;运算符&#xff09;-CSDN博客 Python基础入门例程39-NP39 字符串之间的比较&#xff08;运算符&#xff09;-C…...

C#的LINQ to XML 类中使用最多的三个类:XElement、XAttribute 和 XDocument

目录 一、XElement 类 1.使用 XElement 类创建一个 xml 文档 &#xff08;1&#xff09;示例源码 &#xff08;2&#xff09;xml文件 2.使用LINQ to SQL或者LINQ to Object获取数据源 &#xff08;1&#xff09;示例源码 &#xff08;2&#xff09;xml文件 3.XElement …...

2023软考-系统架构师一日游

上周六&#xff08;11月4号&#xff09;参见了软考&#xff0c;报的系统架构师&#xff0c;今年下半年是第一次推行机考&#xff0c;简单来分享下大致流程&#xff0c;至于考试难度、考点什么的&#xff0c;这个网上有很多专门研究这些的机构&#xff0c;本人无权发言。考试的经…...

维乐 Prevail Glide带你做破风王者,无阻前行!

对于自行车骑手来说&#xff0c;需要应对的问题有很多&#xff0c;其中最大的问题之一&#xff0c;就是「风阻」。风阻永远都是你越反抗越强&#xff0c;因此为了克服风阻的力量&#xff0c;时间久了&#xff0c;身体自然会造成一定程度的损伤。如何才能调整前行的步伐&#xf…...

企业通配符SSL证书的特点

企业通配符SSL证书是一种数字证书&#xff0c;其可以用于保护多个企业网站&#xff0c;对网站传输信息进行加密服务。这种证书通常适用于拥有多个子域名或二级域名的企事业单位。今天就随SSL盾小编了解企业通配符SSL证书的相关信息。 1. 保护所有域名和子域名&#xff1a;企业通…...

1.2 HTML5

一.HTML5 简介 1.什么是HTML5 HTML5是新一代的 HTML 标准&#xff0c;2014年10月由万维网联盟( W3C&#xff09;完成标准制定。官网地址: w3c提供:HTML StandardWHATWG提供: HTML Standard HTML5在狭义上是指新—代的 HTML 标准&#xff0c;在广义上是指:整个前端。 2.HTML…...

一个例子!教您彻底理解索引的最左匹配原则!

最左匹配原则的定义 简单来讲&#xff1a;在联合索引中&#xff0c;只有左边的字段被用到&#xff0c;右边的才能够被使用到。我们在建联合索引的时候&#xff0c;区分度最高的在最左边。 简单的例子 创建一个表 CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT, code…...

Docker容器技术实战4

11、docker安全 proc未被隔离&#xff0c;所以在容器内和宿主机上看到的东西是一样的 容器资源控制 cpu资源限制 top命令&#xff0c;查看cpu使用率 ctrlpq防止退出回收&#xff0c;容器会直接调用cgroup&#xff0c;自动创建容器id的目录 cpu优先级设定 测试时只保留一个cpu…...

vue3中使用better-scroll

文章目录 需求分析安装htmlcssjs 需求分析 假设现在有这么一个需求&#xff0c;页面顶部有几个tabs导航&#xff0c;每一个tab下都有一个可以滑动的切换按钮。咱们就可以引入better-scroll来实现这个需求。 安装 首先下载better-scroll npm install better-scroll/core --…...

RK3568禁用调试口改成普通口

RK3568共10个串口&#xff0c;需要用到8个串口&#xff0c;无耐其他UART都被外设复用了&#xff0c;只好将调试口也拿出来作为普通口&#xff0c;方法&#xff1a;禁用调试口、增加UART2 1. vi kernel/arch/arm64/boot/dts/rockchip/OK3568-C-linux.dts 2. #include &quo…...

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...

React Native 导航系统实战(React Navigation)

导航系统实战&#xff08;React Navigation&#xff09; React Navigation 是 React Native 应用中最常用的导航库之一&#xff0c;它提供了多种导航模式&#xff0c;如堆栈导航&#xff08;Stack Navigator&#xff09;、标签导航&#xff08;Tab Navigator&#xff09;和抽屉…...

PHP和Node.js哪个更爽?

先说结论&#xff0c;rust完胜。 php&#xff1a;laravel&#xff0c;swoole&#xff0c;webman&#xff0c;最开始在苏宁的时候写了几年php&#xff0c;当时觉得php真的是世界上最好的语言&#xff0c;因为当初活在舒适圈里&#xff0c;不愿意跳出来&#xff0c;就好比当初活在…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...

测试markdown--肇兴

day1&#xff1a; 1、去程&#xff1a;7:04 --11:32高铁 高铁右转上售票大厅2楼&#xff0c;穿过候车厅下一楼&#xff0c;上大巴车 &#xffe5;10/人 **2、到达&#xff1a;**12点多到达寨子&#xff0c;买门票&#xff0c;美团/抖音&#xff1a;&#xffe5;78人 3、中饭&a…...

多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验

一、多模态商品数据接口的技术架构 &#xff08;一&#xff09;多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如&#xff0c;当用户上传一张“蓝色连衣裙”的图片时&#xff0c;接口可自动提取图像中的颜色&#xff08;RGB值&…...

【算法训练营Day07】字符串part1

文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接&#xff1a;344. 反转字符串 双指针法&#xff0c;两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)

🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...

Rust 异步编程

Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

数据库分批入库

今天在工作中&#xff0c;遇到一个问题&#xff0c;就是分批查询的时候&#xff0c;由于批次过大导致出现了一些问题&#xff0c;一下是问题描述和解决方案&#xff1a; 示例&#xff1a; // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...