【Java难点】多线程-初级

一锁两并三程

synchronized

并发和并行

并发(concurrent):在一台处理器上“同时”处理多个任务,即有多个任务在单个cpu上交替进行,但其实在同一时刻,只有一个任务在执行。

并行(parallel):在多台处理器上同时处理多个任务,即同一时刻,有多个指令在多个cpu上同时执行。例子:锅在加热的同时你在切菜。

2核4线程:可以最多同时运行4个线程,超过4个线程,则需要在这些线程间交替执行

进程、线程、管程

进程:在系统中运行的一个应用程序就是一个进程,每一个进程都有他自己的内存空间和系统资源。

例子“每个软件就是一个进程,idea、mindmanager2016、sublime、任务管理器都是进程

image-20240421223737828

线程:也被称为轻量级进程,在同一个进程内会有1个或多个线程,是大多数操作系统进行时序调度的基本单元。

例子:idea这个进程下有很多个线程

image-20240421224217721

管程:Monitor(监视器),也就是我们平时所说的锁。监视器是一种同步机制,他的义务是保证(同一时间)只有一个线程可以访问被保护的数据和代码。

例子:使用synchronized时,需要传入一个对象,此时传入的对象o就作为监视器

image-20240421224551988

总结:

执行线程要求先成功持有管程,然后才能执行方法,最后当方法完成(无论是正常完成还是非正常完成)时释放管程。在方法执行期间,执行线程持有了管程,其他任何线程都无法再获取到同一个管程。

多线程的实现方式

  1. 继承Thread类的方式进行实现

image-20240411234800057

  1. 实现Runnable接口的方式进行实现

image-20240412215136257

  1. 利用Callable接口和Future接口方式实现

image-20240412215918591

三种方式对比

image-20240412220057655

Thread类常用的方法

image-20240412221702840

  • setName和getName

设置线程名字的方法

  1. 如果没有给线程设置名字,线程的默认名字为Thread-X(X为序号,从0开始)

image-20240412222105010

注意:main方法所在的线程的名字为main

  1. 可以用setName方法设置线程名字,
  2. 可以在Thread的构造方法中设置线程的名字

image-20240412222327077

注意:使用构造方法设置线程名字时需要在实现类中构造方法中调用父类的构造方法

image-20240412223906940

  • currentThread

currentThread可以获取当前线程的对象,哪条线程执行到这个方法,此时获取的就是那条线程的对象

当JVM虚拟机启动之后,会自动的启动多条线程,其中有一条线程就叫做main线程,他的作用就是去调用main方法,并执行里面的代码。在以前,我们写的所有的代码,其实都是运行在main线程当中

  • sleep

sleep方法可以让线程休眠指定的时间,单位为毫秒(1秒=1000毫秒)。哪条线程执行到这个方法,那么那条线程就会在这里停留对应的时间。 当时间到了之后,线程会自动的醒来,继续执行下面的其他代码

  • setPriority和getPriority

cpu调度方式:

  1. 抢占式调度

多个线程抢夺cpu的执行权,cpu执行哪条线程是不确定的,执行多长时间也是不确定的,抢占式调度具有随机性。

jvm采用抢占式调度方式,所以每个线程何时执行,执行多长时间都是不确定的,线程的优先级越高,抢到cpu执行权的概率越大。

  1. 非抢占式调度

setPriority方法用来设置线程的优先级,优先级默认为5。优先级范围为1-10。

image-20240412230345732

注意:

main线程的默认优先级也是5

  • setDaemon

setDaemon方法用于设置守护线程,setDaemon方法必须用在start方法前。

image-20240412231701918

女神线程执行完毕后,备胎线程没有打印到“备胎@100”就提前结束了

image-20240412232018982

  • isDaemon

判断是否为守护线程

守护线程的应用场景:

  1. 垃圾回收线程

  2. 聊天的同时传输文件。聊天窗口为线程1,传输文件为线程2。关闭聊天窗口(关闭线程1)后,传输文件也会自动断开(关闭线程2)。此时可以把传输文件设置为守护线程,当关闭线程1后,线程2就会自动断开了。

image-20240412232209700

  • yield

yield方法会出让cpu的执行权

image-20240413190645661

  • join

image-20240413191046153

注意:

相当于把线程t加入到了main线程中,即把两个线程合并为单线程

线程的生命周期

image-20240413192145842

**问:**sleep方法会让线程睡眠,睡眠时间到了之后,立马就会执行下面的代码吗?

不会,阻塞状态结束后会进入就绪状态,而不是运行状态

线程的安全问题

需求:

某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票

image-20240413194816053

出现的问题:

问题1:三个窗口同时卖同一张票

image-20240413194858792

问题2:窗口1和窗口3卖的票超出了100张

image-20240413195004133

原因:

一开始ticket=0,窗口1这个线程运行到if(ticket<100)时,符合条件,窗口1进入if的方法体内,运行到Thread.sleep(100)时,窗口1会进入阻塞状态,丢失cpu的执行权,此时cpu的执行权会被窗口2或窗口3抢走,假设窗口2抢到了执行权,此时ticket仍然是2,所以窗口2也会进入到if方法体内,运行到Thread.sleep(100)时,窗口2进入阻塞状态,假设此时窗口1仍处于阻塞状态,此时执行权一定会被窗口3抢走,同样的,此时ticket=2,窗口3运行Thread.sleep(100)时,窗口3进入阻塞状态。假设执行权又被窗口1抢走,窗口1执行完ticket++这条语句,此时ticket=1,碰巧窗口1在执行sout这条语句前,执行权被窗口2抢走,窗口2执行到ticket++后,此时ticket=2,碰巧窗口2执行sout这条语句前,执行权又被窗口3抢走,窗口3执行完ticket++后,此时ticket=3,窗口3继续向下执行,执行sout语句,此时输出“窗口3正在卖第3张票”,执行完sout这条语句后,执行权又被窗口1抢走,窗口1执行sout语句,此时输出“窗口1正在卖第3张票”,窗口2同理…

注意:

线程在执行任意语句后,执行权都概率被其他线程抢走,并不是线程进入阻塞状态后才有概率被其他线程抢走!Thread.sleep(100)只是为了方法这个现象!

解决办法:

办法1:同步代码块

把操作共享数据的代码锁起来

格式:

synchronized (锁对象) {操作共享数据的代码}

特点1:锁默认打开,有一个线程进去了,锁自动关闭

特点2:里面的代码全部执行完毕,线程出来,锁自动打开

同步代码块实现卖票:

image-20240413202800174

办法2:同步方法

就是把synchronized关键字加到方法上

格式:

修饰符 synchronized 返回值类型 方法名(方法参数) {...}

特点1:同步方法是锁住方法里面所有的代码

特点2:锁对象不能自己指定

非静态方法的锁对象是:this

静态方法的锁对象是:当前类的字节码文件对象

同步方法使用技巧:

先写同步代码块,然后将同步代码块改成同步方法

同步方法实现卖票:

  1. 先写成同步代码块的形式

image-20240413213244925

  1. 将同步代码块抽取成同步方法

image-20240413213919747

注意:MyRunnable实现了Runnable接口,开启多线程时使用MyRunnable mr = new MyRunnable(); Thread t1 = new Thread(mr);的方式,因为只创建了一个MyRunnable()对象,三个线程传入了相同的MyRunnable()对象,int ticket不需要加static关键字,因为三个线程访问的是同一个对象的ticket。

Lock锁

image-20240413235301682

实现方式

image-20240414000220356

死锁

男孩(线程1)和女孩(线程2)一起抢饭吃,但是只有两只筷子:筷子A(锁A)和筷子B(锁B)。规则:只有一个人同时拿到两只筷子才能吃饭,吃一口饭后要把筷子放到桌子上(释放锁),两个人重新抢筷子吃饭。

情况1:男孩(线程1)同时拿到筷子A(锁A)和筷子B(锁B),吃一口后放到重新将筷子A(锁A)和筷子B(锁B)桌子上。

情况2:女孩(线程2)同时拿到筷子A(锁A)和筷子B(锁B),吃一口后放到重新将筷子A(锁A)和筷子B(锁B)桌子上。

情况3:男孩(线程1)拿到筷子A(锁A),女孩(线程2)拿到筷子B(锁B),两个人都等待对方吃一口饭后放下筷子(释放锁)

情况4:男孩(线程1)拿到筷子B(锁B),女孩(线程2)拿到筷子A(锁A),两个人都等待对方吃一口饭后放下筷子(释放锁)

情况3和情况4就发生了死锁错误。

生产者和消费者(等待唤醒机制)

image-20240414003030443

常见方法:

image-20240414003114823

需求:

完成生产者和消费者(等待唤醒机制)的代码,实现线程轮流交替执行的效果

多线程的状态

image-20240414011623466

注意:

这些状态是运行在jvm态,并没有反应所有操作系统线程状态。

线程抢到cpu的执行权后,jvm就会把该线程交给操作系统,所以jvm上线程是没有运行状态的。

image-20240414012317254

线程池

线程池的主要原理:

image-20240414013250855

创建线程池的方法:

使用线程池工具类:Executors。通过调用Executors的静态方法来创建不同类型的线程池对象。

image-20240414013437791

使用线程池:

  1. 创建没有大小限制的线程池(线程池的大小为int所能表述的最大值)

  1. 创建固定大小的线程池

image-20240415223140904

image-20240415223057786

自定义线程池

自定义线程池的7个参数:

image-20240416004358917

注意1:

临时线程对象的最大数量=线程池中最大线程的数量-核心线程数量;

空闲时间指的是临时线程对象多长时间没有工作,则会将临时线程对象销毁。

注意2:

来任务了需要先创建核心线程对象,核心线程对象都是工作状态,再来任务的话会进入核心线程的队列中等待,核心线程队列满了才会创建临时线程对象;

线程执行顺序不是按照先来后到的规则,队伍里的线程会按照先来后到的规则执行等待核心线程执行,而队伍外的线程会使用临时线程执行;

核心线程对象都是工作状态,核心线程队伍满了,临时线程对象也都是工作状态,此时再来任务,会触发任务拒绝策略,默认拒绝策略是舍弃不要。

image-20240416005342773

任务拒绝策略:

image-20240416004305741

代码实现:

image-20240416004745908

  • 24
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,您的问题是关于Java多线程的存钱取钱问题。这是一个经典的多线程同步问题,可以通过使用synchronized关键字来实现线程安全。下面是一个简单的示例代码: ```java public class BankAccount { private int balance; public BankAccount(int balance) { this.balance = balance; } public synchronized void deposit(int amount) { balance += amount; System.out.println("Deposit successful. Balance: " + balance); } public synchronized void withdraw(int amount) { if (balance < amount) { System.out.println("Withdraw failed. Insufficient balance."); return; } balance -= amount; System.out.println("Withdraw successful. Balance: " + balance); } } public class Main { public static void main(String[] args) { BankAccount account = new BankAccount(1000); // Create two threads to simulate deposit and withdraw Thread depositThread = new Thread(() -> { for (int i = 0; i < 5; i++) { account.deposit(100); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread withdrawThread = new Thread(() -> { for (int i = 0; i < 5; i++) { account.withdraw(200); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); depositThread.start(); withdrawThread.start(); } } ``` 在这个示例代码中,我们创建了一个银行账户类BankAccount,并在其中实现了deposit和withdraw方法,并使用synchronized关键字来保证线程安全。 在main方法中,我们创建了两个线程来模拟存款和取款操作,每个线程执行5次操作。我们使用Thread.sleep方法来模拟每个操作之间的间隔,以便更好地观察多线程操作的结果。 当多个线程同时访问BankAccount对象的deposit和withdraw方法时,synchronized关键字可以确保每个方法只能被一个线程访问,从而避免了竞争条件和数据不一致的问题。 希望这个示例代码能够回答您的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值