线程安全
2025-4-20
| 2025-4-20
Words 2749Read Time 7 min
type
status
date
slug
summary
tags
category
icon
password
本节完成线程安全相关知识,线程同步,同步代码块,同步方法,死锁相关知识。

电影院售票

部分结果:

可以看到出现了重复售票的情况,票号缺少的情况,还出现了超卖的情况。

同步代码块

上述问题的原因
在执行 sleep 的时候,cpu 的执行权会被释放。
此时可能有其他线程进来,当 ticket++之后还未打印的时候,其他线程把 cpu 的执行权拿走了,又对 ticket 进行++了,所以最后打印的时候就会错过某些票号,并且重复打印某些票号。同理,当此时 ticket=99 的时候,三个线程可能同时进来进行++,导致了超卖的情况。
notion image
解决方案
把执行逻辑的代码锁起来,同一时刻只能有一个线程在运行。
notion image
sybchronized
特点 1:锁默认是打开的,有一个线程进去了,锁自动关闭
特点2:里面的代码全部执行完毕之后,线程出来,锁自动打开

代码

同步代码块细节
synchronized里面的对象必须是唯一的对象,如果不是唯一的对象,那么代表线程看的是不同的锁,比如里面是synchronized(this),,this 代表这个线程对象本身,这样就会出现上述情况,因为不同线程看的是不同的锁。
通常是用synchronized(MyThread.class),通常是用本类的 class,因为不管有多少个对象,class 文件只有一个。

同步方法

上面是同步代码块,也就是把一段代码用synchronized包括起来,那我们同样也可以把那段代码抽象出来形成一个方法。
同步方法之 StringBuilder 和 StringBuffer
可以看到两个对象的方法是差不多的,Stringbuffer 使用了同步方法。
notion image

lock

上面使用 synchronized有一个问题是我们无法控制加锁和释放锁的位置,如果我们想手动加锁和释放锁的的时候可以使用 lock。
可以看到,可以使用static Lock lock = new ReentrantLock();,lock 对象手动进行加锁释放锁。
问题:
发现代码运行完之后没有停。
notion image
原因
因为当 ticket=100 的时候,会执行 else 的 break代码,跳出 while 循环,不会执行 lock.unclock()方法。所以可以使用 finally 代码块进行释放锁。

死锁

比如说两把锁,线程 1 拿到锁A,准备拿锁 B,此时线程 2拿到了锁 B等待拿锁 A,就会造成卡死,也就是死锁。

解决死锁:等待唤醒机制

上面出现死锁的原因是,双方线程都在等待对方释放锁。那么只要让一方先妥协,也就是让一方等待,等待之后,另一方就是拿到锁,他执行完之后再唤醒等待的线程即可。
简单来说,原本是规律执行,产生了死锁,现在轮流执行。
涉及到的方法
notion image

阻塞队列

上面的例子是每次生产者和消费者只处理一条数据,假如说想同时处理多条数据,可以在生产者和消费者之间放一个队列,生产者将生成的数据put 队列,消费者从队列中取出消费。
阻塞:生产者 put 数据时,放不进去,会等着,叫做阻塞。消费者取数据的时候取不到,也叫做阻塞。

代码实现

消费者

生产者

执行

结果

看到结果是有连续的,正常队列只有一个,应该是生产一个消费一个,因为打印的语句在锁的外面,也就是在 put 和 take 外面,造成的。
notion image
notion image

线程的状态

新建线程对象,进入就绪状态,拿到 cpu 的执行权运行,执行完毕线程死亡。在线程执行的时候,无法获得锁就会进入阻塞状态,重新拿到锁就进入就绪状态。如果遇到了 wait 语句,则进入等待状态,唤醒之后进入就绪状态,遇到 sleep进入计时等待状态,结束之后重新进入就绪状态。
notion image

线程池

跟数据库连接池是一样的,是防止线程频繁创建和销毁导致的效率低的问题。

线程池主要核心原理

1、创建一个池子,池子中是空的。
2、提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下次再提交任务时,不需要重新创建线程,直接复用已有的线程即可。
3、但是如果提交任务时,池子里没有空闲线程,也无法创建新的线程,任务就会排队等待。

代码实现

线程池执行的任务

自定义线程池

使用 ThreadPool 创建线程池
线程池核心参数:
核心线程数量:不会被销毁。
线程池中最大线程的数量:加上创建的临时线程
空闲时间:临时线程在过了多长时间还未接到任务被辞退
排队的人过多拒绝服务的策略。
当核心线程数,临时线程数,队伍长度各有三个的时候。任务 1,2,3 分别为线程 1,2,3 执行,任务 4,5,6 会放入等待队列中,任务 7,8 会创建两个临时线程 4,5 执行。
notion image
如果任务再多的话,就会执行任务拒绝策略。
默认会丢弃任务,抛出异常
丢弃任务丢弃异常
抛弃队列中等待最久的任务,然后把当前任务加入队列中,上面例子中就是丢弃任务 4
直接调用 run 方法,不再创建新的线程执行任务。
notion image

代码实现

组件简介简单实现Spring容器
Loading...