Java基础-多线程基础

文章目录

    • 1.线程相关概念
          • 1.程序
          • 2.进程
          • 3.线程
          • 4.单线程
          • 5.多线程
          • 6.并发
          • 7.并行
          • 查看当前电脑cpu数量
    • 2.线程基本使用
        • 1.线程类图
        • 2.继承Thread创建线程
          • 细节说明
          • 代码实例
        • 3.实现Runnable来创建线程(包括静态代理演示)
          • 代码实例
    • 3.多线程机制
        • 简要介绍
        • 代码实例
        • 为什么使用start开启线程?
          • 追源码
          • 示意图
    • 4.多个子线程案例
        • 题目1
          • 代码实例
          • 结果
        • 继承Thread和实现Runnable的区别
        • 题目2:多线程售票问题
          • 代码实例
          • 结果
          • 问题
    • 5.线程终止
        • 通知线程终止方法
          • 代码实例
          • 结果
    • 6.线程常用方法
        • 常用方法第一组(线程中断)
          • 注意事项
          • 代码实例
          • 结果
        • 常用方法第二组(线程插队)
          • 代码实例
          • 结果
        • 线程插队练习
          • 题目
          • 代码
          • 结果
        • 用户线程和守护线程
          • 基本介绍
          • 设置守护线程的代码实例
          • 结果
    • 7.线程的七大状态
          • 状态介绍
          • 代码实例
          • 结果
    • 8.线程同步机制
        • 基本介绍
        • 实现同步的具体方法
        • 互斥锁
        • 使用两种方法实现卖票(在方法上加锁)
          • 注意事项
          • 代码实例
        • 使用synchronized代码块的方式加锁(推荐)
          • 细节说明
          • 代码实例
          • 结果
    • 9.线程的死锁
        • 基本介绍
        • 代码实例
        • 结果
    • 10.释放锁
        • 以下操作会释放锁
        • 以下操作不会释放锁
    • 11.多线程练习题
        • 题目一
          • 代码
          • 结果
        • 题目二
          • 代码
          • 结果

1.线程相关概念

1.程序

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

2.进程
  1. 指运行中的程序,没启动一个进程,操作系统就会为这个进程分配新的内存空间。比如启动了迅雷,系统就会为他分配内存空间
  2. 进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程,有他自身产生、存在和消亡的过程
3.线程
  1. 线程是由进程创建的,是进程的一个实体
  2. 一个进程可以拥有多个线程
  3. 就相当于迅雷是一个进程,创建了多个下载线程image-20240107164210160
4.单线程

同一个时刻,只允许执行一个线程

5.多线程

同一个时刻,可以执行多个线程,比如:一个qq进程可以同时打开多个聊天窗口

6.并发

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

image-20240107165446496

7.并行

同一时刻,多个任务并发执行,多个cpu可以实现并行

image-20240107165453621

查看当前电脑cpu数量
/*** @author 孙显圣* @version 1.0*/
public class CpuNum {public static void main(String[] args) {Runtime runtime = Runtime.getRuntime(); //单例模式,获取Runtime实例int cpuNum = runtime.availableProcessors(); //获取当前cpu数量System.out.println(cpuNum);}
}

image-20240107170143573

2.线程基本使用

1.线程类图

image-20240107170445239

2.继承Thread创建线程
细节说明
  1. 当一个类继承了Thread类,该类就可以当做线程使用
  2. 我们会重写run方法,写上自己的业务代码
  3. Thread类,实现了Runnable接口的run方法
代码实例
package Thread_;/*** @author 孙显圣* @version 1.0*/
public class Thread01 {public static void main(String[] args) {Cat cat = new Cat(); //创建线程对象cat.start();//线程开始}
}class Cat extends Thread { //继承Thread类,重写run方法来写自己的逻辑int num = 0; //统计次数@Overridepublic void run() {while (true) {System.out.println("猫猫在跳舞" + (++num));try {Thread.sleep(1000); //线程休眠1秒} catch (InterruptedException e) {throw new RuntimeException(e);}if (num == 8) { //次数为8次则退出循环,从而结束run方法,结束线程break;}}}
}

image-20240107172638559

3.实现Runnable来创建线程(包括静态代理演示)
代码实例
package Thread_;import org.junit.jupiter.api.Test;/*** @author 孙显圣* @version 1.0*/
public class Thread02 {public static void main(String[] args) {Dog dog = new Dog(); //创建线程类的实例Thread thread = new Thread(dog); //将线程类的实例传到Thread中thread.start(); //启动线程调用线程类的run方法}//用来测试静态代理模式@Testpublic void test() {Tiger tiger = new Tiger();Thread_ thread = new Thread_(tiger); //将实现了Runnable接口的类的实例传进去thread.start();}
}//继承Runnable接口来创建线程
class Dog implements Runnable {@Overridepublic void run() {System.out.println("Dog线程启动");}
}class Tiger implements Runnable {@Overridepublic void run() {System.out.println("老虎在叫");}
}//模拟Thread,静态代理模式
class Thread_ implements Runnable {private Runnable target = null; //使用属性接受那个实现了Runnable接口的实例public Thread_(Runnable target) { //传入一个实现了Runnable接口的实例this.target = target;}@Overridepublic void run() {if (target != null) {target.run();}}public void start() { //1.创建当前实例,调用start方法start0();}public void start0() { //2.调用runrun();}}

image-20240107202443199

3.多线程机制

简要介绍
  1. 当程序开始执行的时候,系统会开启一个进程,可以在Terminal输入jconsole可以查看
  2. 这个进程会先开启main线程,然后在下面的代码实例中可以看出main线程开启了一个子线程Cat
  3. 这样主线程并不会被阻塞,而是会和子线程交替执行
代码实例
package Thread_;/*** @author 孙显圣* @version 1.0*/
public class Thread01 {public static void main(String[] args) throws InterruptedException {Cat cat = new Cat(); //创建线程对象cat.start();//线程开始//当main线程启动一个子线程的时候,主线程不会阻塞,会继续执行for (int i = 0; i < 60; i++) {System.out.println("主线程" + i);Thread.sleep(1000); //主线程休眠}}
}class Cat extends Thread { //继承Thread类,重写run方法来写自己的逻辑int num = 0; //统计次数@Overridepublic void run() {while (true) {System.out.println("猫猫在跳舞" + (++num));try {Thread.sleep(1000); //线程休眠1秒} catch (InterruptedException e) {throw new RuntimeException(e);}if (num == 80) { //次数为8次则退出循环,从而结束run方法,结束线程break;}}}
}

image-20240107184358023

为什么使用start开启线程?
追源码
  1. 打断点image-20240107185327269
  2. 跳入,走几步image-20240107185416568
  3. 这里实际上是调用了start0方法,而这个方法是native的方法,是虚拟机调用的,用来启动线程,可以理解为是虚拟机调用了run方法
  4. 注意:如果直接调用run方法并不能开启线程,只能说是串行化执行,它将会作为一个普通方法在main线程里串行化执行
示意图

image-20240107185821671

4.多个子线程案例

题目1

image-20240107202616489

代码实例
package Thread_;/*** @author 孙显圣* @version 1.0*/
public class Thread03 {public static void main(String[] args) {//启动第一个线程Thread1 thread1 = new Thread1();thread1.start();//启动第二个线程Thread2 thread2 = new Thread2();Thread thread = new Thread(thread2);thread.start();}
}// 第一个线程继承Thread类
class Thread1 extends Thread {private int count = 0;@Overridepublic void run() {while (true) {System.out.println("hello world");count++;try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}if (count == 10) {break;}}}
}// 第二个线程实现Runnable接口
class Thread2 implements Runnable {private int count = 0;@Overridepublic void run() {while (true) {System.out.println("hi");count ++;try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}if (count == 5) {break;}}}
}
结果

image-20240107204606732

继承Thread和实现Runnable的区别
  1. 通过继承Thread和实现Runnable接口来创建线程本质上并没有区别
  2. 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制(建议使用)

image-20240107205225393

题目2:多线程售票问题

image-20240108105244964

代码实例
package Thread_;/*** @author 孙显圣* @version 1.0* 多线程售票*/
public class SellTicket {public static void main(String[] args) {//创建三个线程
//        for (int i = 0; i < 3; i++) {
//            new SellWin01().start();
//        }//创建三个线程,使用实现接口的方法for (int i = 0; i < 3; i++) {new Thread(new SellWin02()).start();}}
}class SellWin01 extends Thread {public static int tickets = 100; //多个实例共享100张票@Overridepublic void run() { //线程操作:售票while (true){ //循环卖票//判断退出条件if (SellWin01.tickets == 0) {return;}//卖票System.out.println(Thread.currentThread().getName() + "卖出一张票,还剩" + (--SellWin01.tickets) + "张票");//休眠try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}class SellWin02 extends SellWin01 implements Runnable {
}
结果

image-20240108105502913

问题
  1. 多个线程出现卖的是一张票的问题
  2. 说明在第一个线程没有卖完,第二个线程就进去了并且也卖票了

5.线程终止

通知线程终止方法
  1. 通过使用变量,来控制run方法退出
  2. 将一个布尔型变量设置为私有的,然后设置他的set方法
  3. 使用这个布尔型变量控制循环,这样就可以在其他线程来设置这个变量从而控制线程退出
代码实例
package Thread__;/*** @author 孙显圣* @version 1.0*/
public class ThreadExit {public static void main(String[] args) throws InterruptedException {//开启子线程ThreadExit_ threadExit = new ThreadExit_();new Thread(threadExit).start();Thread.sleep(10*1000); //主线程睡眠10s//通知子线程退出threadExit.setLoop(false);}
}//写一个线程
class ThreadExit_ implements Runnable {private boolean loop = true; //用来控制循环public void setLoop(boolean loop) { //使用主线程来控制这个循环条件this.loop = loop;}@Overridepublic void run() {while (loop) {System.out.println("子线程运行中");try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("子线程退出");}
}
结果

image-20240108111258251

6.线程常用方法

常用方法第一组(线程中断)

image-20240108134009741

注意事项
  1. start底层会创建新的线程,调用run,run是一个简单的方法调用,不会启动新的线程
  2. 线程优先级的范围(1到10)image-20240108134429741
  3. interrupt,中断线程,但是并没有真正的结束线程,所以一般用于中断正在休眠的线程
  4. sleep:线程的静态方法,使当前线程休眠
代码实例
package Thread__;/*** @author 孙显圣* @version 1.0*/
public class ThreadMethod01 {public static void main(String[] args) {//主线程设置一个子线程ThreadM threadM = new ThreadM();// setName 设置线程名字threadM.setName("Sun");// setPriority 设置线程优先级threadM.setPriority(Thread.MIN_PRIORITY); //这里是调用了Thread的默认静态常量设置的优先级为1// 获取线程名字和优先级System.out.println(threadM.getName() + "的优先级为" + threadM.getPriority());//启动线程threadM.start();//主线程打印十个hellofor (int i = 0; i < 10; i++) {System.out.println("hello");}//调用线程中断,中断线程的休眠,执行中断逻辑threadM.interrupt();}
}//写一个线程类
class ThreadM extends Thread {@Overridepublic void run() {//线程一直执行while (true) {//吃十个包子for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + "吃包子" + i); //获取当前线程的名字}//然后休眠10stry {Thread.sleep(10*1000);} catch (InterruptedException e) { //这个捕捉的就是中断异常,在main线程中调用中断则会触发// 这里可以写上自己的业务逻辑System.out.println("吃包子的休眠被中断了!!!");}//在这里中断逻辑处理完之后会继续循环}}
}
结果

image-20240108135813332

常用方法第二组(线程插队)

image-20240108142054091

代码实例
package Thread__;/*** @author 孙显圣* @version 1.0*/
public class ThreadMethod02 {public static void main(String[] args) throws InterruptedException {//启动子线程ThreadQueueCutting threadQueueCutting = new ThreadQueueCutting();Thread thread = new Thread(threadQueueCutting);thread.start();//主线程每隔一秒输出hi,输出20次int count = 0;for (int i = 0; i < 20; i++) {//在输出完五次的时候,让子线程插队,会先执行完子线程所有的内容,然后再继续执行主线程if (count == 5) {
//                thread.join(); //线程插队Thread.yield(); //线程礼让,如果资源丰富则不会成功}System.out.println("hi");count ++;try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}//子线程,每隔一秒输出hello, 输出20次
class ThreadQueueCutting implements Runnable {@Overridepublic void run() {for (int i = 0; i < 20; i++) {System.out.println("hello");//间隔一秒try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
结果

image-20240108142244685

线程插队练习
题目

image-20240108143120549

代码
package Thread__;/*** @author 孙显圣* @version 1.0*/
public class ThreadMethodExercise {public static void main(String[] args) throws InterruptedException {//启动子线程ThreadCut threadCut = new ThreadCut();threadCut.start();//主线程,每隔1s,输出hi,一共10次for (int i = 0; i < 10; i++) {if (i == 5) {threadCut.join(); //当执行完五次之后子线程插队}System.out.println(Thread.currentThread().getName() + " : hi!");Thread.sleep(1000);}}
}//子线程,每隔1s输出hello,输出10次
class ThreadCut extends Thread {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.cur  entThread().getName() + " : hello!");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
结果

image-20240108143154528

用户线程和守护线程
基本介绍
  1. 用户线程:也叫工作线程,当线程的任务执行完成或以通知方式结束
  2. 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
  3. 常见的守护线程:垃圾回收机制
设置守护线程的代码实例
package Thread__;/*** @author 孙显圣* @version 1.0*/
public class ThreadMethod03 {public static void main(String[] args) throws InterruptedException {//设置子线程为守护线程,这样即使他是无限循环的,但是,只要没有线程正在执行,那么他就会退出DaemonThreads daemonThreads = new DaemonThreads();Thread thread = new Thread(daemonThreads);thread.setDaemon(true);thread.start();//主线程for (int i = 0; i < 10; i++) {System.out.println("宝强在辛苦的工作");Thread.sleep(1000);}}
}//守护线程
class DaemonThreads implements Runnable {@Overridepublic void run() {for (;;) { //无限循环System.out.println("马蓉和宋吉在快乐的聊天");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
结果

image-20240108145209798

7.线程的七大状态

状态介绍
  1. NEW
  2. Runnable
    1. Ready
    2. Running
  3. TimeWaiting(超时等待,调用sleep时)
  4. Waiting(等待,调用join时)
  5. Blocked
  6. Teminated(终止)

image-20240108152228764

代码实例
package Thread__;/*** @author 孙显圣* @version 1.0*/
public class ThreadState {public static void main(String[] args) throws InterruptedException {ThreadS threadS = new ThreadS();System.out.println(threadS.getState()); //NEW状态threadS.start();while (threadS.getState() != Thread.State.TERMINATED) { //只要子线程的状态不是终止状态,就一直输出子线程的状态System.out.println(threadS.getState());Thread.sleep(1000);}//当子线程结束时输出子线程的状态System.out.println(threadS.getState());}
}//子线程
class ThreadS extends Thread {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println("执行" + i);try {Thread.sleep(500); //可以引起超时等待状态TimneWaiting} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
结果

image-20240108153336183

8.线程同步机制

基本介绍

image-20240108164116684

实现同步的具体方法

image-20240108164049286

互斥锁
  1. 使用关键字synchronized来与对象的互斥锁联系
  2. 虽然实例方法的代码是所有实例共享的,但是在并发中,每个实例对象拥有独立的实例方法(这么理解)
  3. 非静态同步:直接使用synchronized,这样的同步是对象锁,也就是给每个对象的实例方法加锁,这样其他非本类型的实例只能同时有一个访问本类型对象的实例方法
  4. 静态同步:使用synchronized static, 这样的同步是类锁,由于加了static,则这个方法是被本类型的所有实例共享的,这样本类型的所有实例只能同时有一个访问这个静态方法
  5. 简单来说静态同步是对本类型实例实现互斥的,非静态同步是对非本类型实例实现互斥的
使用两种方法实现卖票(在方法上加锁)
注意事项
  1. 这个继承Thread类的就相当于一个线程,所以要创建三个线程实例并访问run方法的sell方法,这就是本类型的互斥(静态同步)
  2. 而实现了Runnable接口的类X并不是一个线程,而Thread类的对象才是一个线程,如果是创建了X的对象,并创建了三个Thread类的对象来调用X的方法,那么对于X的方法sell来说就是要实现非本类型的互斥(非静态同步)
  3. 同步方法如果没用static修饰,默认锁对象为this
  4. 同步方法如果使用static修饰,默认锁对象为当前类.class
代码实例
package Thread__;/*** @author 孙显圣* @version 1.0*/
public class Syn {public static void main(String[] args) {//        for (int i = 0; i < 3; i++) { //三个线程对象,访问三个对象各自的方法,所以要使用静态同步
//            new SellTickets().start();
//        }SellTickes_ sellTickes = new SellTickes_(); //三个线程访问一个对象的同一个,所以使用非静态同步(对象锁)即可for (int i = 0; i < 3; i++) {new Thread(sellTickes).start();}}
}// synchronized解决售票问题,使得同时只能有一个线程售票// 使用静态同步
class SellTickets extends Thread {public static int ticket = 100; //100张票@Overridepublic void run() {while (true) { //一直售票,当票数为0则退出try {sell();Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}}}//售票方法:同时只能有一个线程调用,这里必须将方法设置成静态的,因为只有这样所有的实例才共享这个方法的锁public synchronized static void sell() throws InterruptedException {if (SellTickets.ticket <= 0) {System.exit(0); //只要有一个线程进入这个方法,判断票数不够,就直接系统退出}System.out.println(Thread.currentThread().getName() + "售出一张票,剩余 " + (--SellTickets.ticket));}
}// 使用对象锁
class SellTickes_ implements Runnable {@Overridepublic void run() {while (true) { //一直售票,当票数为0则退出try {sell();Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}}}public synchronized  void sell() throws InterruptedException { //对象锁if (SellTickets.ticket <= 0) {System.exit(0); //只要有一个线程进入这个方法,判断票数不够,就直接系统退出}System.out.println(Thread.currentThread().getName() + "售出一张票,剩余 " + (--SellTickets.ticket));}
}

image-20240108171022324

使用synchronized代码块的方式加锁(推荐)
细节说明
  1. 类锁:当多个实例访问一个类来调用run方法的时候使用
  2. 对象锁:当多个实例访问一个实例来调用那个实例的run方法的时候使用,一般使用this,但是其实只要这个对象是多个实例共享的也可以,是几个对象访问的都要是同一个,可以理解为这个对象就是门的钥匙,但是只有一把
  3. 对象锁举一个例子:就是我下面的那个对象锁,使用的是Dog类(随便的一个类),原因是使用实现Runnable的方式其实是几个Thread实例访问SellT2的实例,从而调用run方法,所以需要使用对象锁,而他们每次访问的一定是一个对象,所以我在类中初始化了一个Dog类,这个类是属于对象的在内存中唯一的一个实例,所以就相当于把这个对象当做一个钥匙,其他实例通过抢它才能够得到进入代码块的机会
  4. 再简单点来说,只要是多个线程共享的那个资源就可以当锁,因为线程需要取得那个资源才能进入代码块
  5. 一个线程类的实例就相当于一个线程,每个实例都拥有自己的run方法代码(这样理解)
代码实例
package Thread__;/*** @author 孙显圣* @version 1.0*/
public class Syn_ {public static void main(String[] args) {
//        for (int i = 0; i < 3; i++) {
//            new SellT1().start(); //开启线程
//        }SellT2 sellT2 = new SellT2();for (int i = 0; i < 3; i++) {new Thread(sellT2).start();}}
}//使用继承Thread的方式——类锁
class SellT1 extends Thread {public static int sticket = 100; //100张票@Overridepublic void run() {while (true) {//这个方式是创建三个实例来调用本类的方法,所以应该使用类锁synchronized (SellT1.class) {//在这里写售票逻辑if (SellT1.sticket <= 0) {return;}System.out.println(Thread.currentThread().getName() + "已经售出一张票,剩余 " + (-- SellT1.sticket));}try {Thread.sleep(10); //休眠一会} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}//使用实现Runnable的方式——对象锁
class SellT2 implements Runnable {public static int sticket = 100;Dog dog = new Dog();@Overridepublic void run() {while (true) {//这个方式是创建三个Thread类型的实例来访问一个类型的实例,所以使用对象锁synchronized (dog) { //这个实例锁,可以是任何类型,只要是几个实例共享的即可if (SellT2.sticket <= 0) {return;}System.out.println(Thread.currentThread().getName() + "已经售出一张票,剩余 " + (-- SellT2.sticket));}try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
结果

image-20240108194531716

9.线程的死锁

基本介绍

image-20240108203508949

代码实例
package Thread__;/*** @author 孙显圣* @version 1.0*/
public class DeadLock {public static void main(String[] args) {new DeadLockDemo(false).start();new DeadLockDemo(true).start();}
}// 线程类
class DeadLockDemo extends Thread {//两个Object类型的互斥锁,满足多个线程共享的条件private static Object lock1 = new Object();private static Object lock2 = new Object();boolean flag;public DeadLockDemo(boolean flag) {this.flag = flag;}@Overridepublic void run() {if (flag == false) {synchronized (lock1) { //第一个线程会抢占这个lock1,再想抢lock2但是lock2被第二个线程先抢到了,所以无法释放lock1System.out.println("进入1");synchronized (lock2) {System.out.println("进入2");}}}else {synchronized (lock2) { //第二个线程会抢占这个lock2,再想抢lock1但是lock1被第一个线程先抢到了,所以无法释放lock2System.out.println("进入2");synchronized (lock1) {System.out.println("进入1");}}}}
}
结果

10.释放锁

以下操作会释放锁

image-20240108211024357

以下操作不会释放锁

image-20240108211219564

11.多线程练习题

题目一

image-20240109093008556

代码
package Thread__;import java.util.Scanner;/*** @author 孙显圣* @version 1.0*/
public class HomeWork01 {public static void main(String[] args) {Th1 th1 = new Th1();new Th2(th1).start();th1.start();}
}class Th1 extends Thread {//设置一个私有属性通知变量private boolean loop = true;//设置get和set方法public boolean isLoop() {return loop;}public void setLoop(boolean loop) {this.loop = loop;}@Overridepublic void run() {while (loop){System.out.println(((int) (Math.random() * 100))); // 0 到 100try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}class Th2 extends Thread {//线程一类型的属性private Th1 th1;//设置一个构造方法获取线程一的实例public Th2(Th1 th1) {this.th1 = th1;}@Overridepublic void run() {Scanner scanner = new Scanner(System.in);while (true) {System.out.print("请输入英文字母:");char re = scanner.next().charAt(0);if (re == 'Q') {System.out.println("通知线程一退出");th1.setLoop(false);}}}
}
结果

image-20240109093126638

题目二

image-20240109093145253

代码
package Thread__;/*** @author 孙显圣* @version 1.0*/
public class HomeWork02 {public static void main(String[] args) {Tori tori = new Tori();for (int i = 0; i < 2; i++) {new Thread(tori).start();}}
}// 取钱线程类
class Tori implements Runnable {//设计一个实例变量即可,因为这个实例变量就是两个线程共享的了(原因是这个是实现接口的方式)private double sal = 10000;@Overridepublic void run() {//一直取钱while (true) {//取钱逻辑需要加锁synchronized (Tori.class) { //这里加一个类锁就可以,因为是多线程共享的if (sal <= 0) {return;}System.out.println(Thread.currentThread().getName() + "取出1000,剩余" +  (sal -= 1000));}try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
结果

image-20240109095331781

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/572469.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

day 36 贪心算法 part05● 435. 无重叠区间 ● 763.划分字母区间 ● 56. 合并区间

一遍过。首先把区间按左端点排序&#xff0c;然后右端点有两种情况。 假设是a区间&#xff0c;b区间。。。这样排列的顺序&#xff0c;那么 假设a[1]>b[0],如果a[1]>b[1]&#xff0c;就应该以b[1]为准&#xff0c;否则以a[1]为准。 class Solution { public:static bo…

相位解包裹前识别有效区域和无效区域(条纹和背景区域区分)

对于不连续场进行相位解包的时候,首先要识别出图象中的哪些部分为有效数据,哪些部分为非有效数据"。这不仅关乎着相位解包算法的速度,更影响着解包算法的精度。因此在解包之前,对有效区域和无效区域的判断必须是首先要做的一件事情。下面就来介绍一下什么是有效区域和…

C语言程序编译与链接(拓宽视野的不二之选)

文章目录 翻译环境和运行环境翻译环境预处理编译汇编链接 运行环境 翻译环境和运行环境 1&#xff0c;在ANSI C的任何⼀种实现中&#xff0c;存在两个不同的环境。 第1种是翻译环境&#xff0c;在这个环境中源代码被转换为可执⾏的机器指 令&#xff08;⼆进制指令&#…

高级数据结构与算法习题(5)

一、单选题 1、Which of the following binomial trees can represent a binomial queue of size 42? A.B0​ B1​ B2​ B3​ B4​ B5​ B.B1​ B3​ B5​ C.B1​ B5​ D.B2​ B4​ 解析:B。要表示一共含有42个节点的二项队列,我们不妨将42表示成为一个二进制形式:,…

iOS_convert point or rect 坐标和布局转换+判断

文章目录 1. 坐标转换2. 布局转换3. 包含、相交 如&#xff1a;有3个色块 let view1 UIView(frame: CGRect(x: 100.0, y: 100.0, width: 300.0, height: 300.0)) view1.backgroundColor UIColor.cyan self.view.addSubview(view1)let view2 UIView(frame: CGRect(x: 50.0, …

连接数据库(MySQL)的JDBC

目录 JDBC简介快速入门API详解DriverManager&#xff08;驱动管理类&#xff09;注册驱动&#xff1a;获取数据库连接(对象)&#xff1a; Connection&#xff08;数据库连接对象&#xff09;获取执行SQL的对象管理事务 Statement(执行SQL语句)执行DML、DDL语句执行DQL语句 Resu…

BaseDao封装JavaWeb的增删改查

目录 什么是BaseDao&#xff1f; 为什么需要BaseDao&#xff1f; BaseDao的实现逻辑 什么是BaseDao&#xff1f; Basedao 是一种基于数据访问对象&#xff08;Data Access Object&#xff09;模式的设计方法。它是一个用于处理数据库操作的基础类&#xff0c;负责封装数据库…

深度学习目标检测算法之RetinaNet算法

文章目录 前言RetinaNet 算法原理1.RetinaNet 简介2.backbone 部分3.FPN特征金字塔4.分类和预测5.Focal Loss 结束语 &#x1f482; 个人主页:风间琉璃&#x1f91f; 版权: 本文由【风间琉璃】原创、在CSDN首发、需要转载请联系博主&#x1f4ac; 如果文章对你有帮助、欢迎关注…

代码学习记录29----贪心算法

随想录日记part29 t i m e &#xff1a; time&#xff1a; time&#xff1a; 2024.03.27 主要内容&#xff1a;今天深入学习贪心算法&#xff0c;接下来是针对题目的讲解&#xff1a;1. 无重叠区间 2.划分字母区间 3. 合并区间 435. 无重叠区间763.划分字母区间56. 合并区间 T…

堆排序基础知识

堆排序基础知识 一、引言二、堆的基本概念三、堆排序的基本思想四、堆排序的详细过程五、堆排序的性能分析六、堆排序的应用七、堆排序的优缺点八、堆排序的实现技巧九、总结与展望 一、引言 堆排序是一种有效的排序算法&#xff0c;它的核心在于使用了一种称为“堆”的数据结…

计算机组成原理-6-计算机的运算方法

6. 计算机的运算方法 文章目录 6. 计算机的运算方法6.1 机器数的表示6.1.1 无符号数和有符号数6.1.2 有符号数-原码6.1.3 有符号数-补码6.1.4 有符号数-反码6.1.5 有符号数-移码6.1.6 原码、补码、反码的比较 6.2 数的定点表示和浮点表示6.2.1 定点表示6.2.2 浮点表示6.2.3 ΔI…

Day20:LeedCode 654.最大二叉树 617.合并二叉树 700.二叉搜索树中的搜索 98.验证二叉搜索树

654. 最大二叉树 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点&#xff0c;其值为 nums 中的最大值。递归地在最大值 左边 的 子数组前缀上 构建左子树。递归地在最大值 右边 的 子数组后缀上 构建右子树。 返回 nums …