线程
线程、进程、协程区别
- 进程是资源分配的最小单位。进程是程序执行的基本单位,进程是程序执行一次的过程,也就是说,一个程序就代表了进程从创建到运行再到消亡的整个过程
- 线程是CPU调度的最小单位。线程是包含于进程的,一个进程可以对应多个进程,但是一个线程只能对应一个进程,与进程不同的是同类的线程共享进程中的堆和方法区,但是他们有着属于自己的本地方法栈、虚拟机栈、程序计数器
- 多进程、多线程区别:
- 共同点 :表示可以同时执行多个任务,进程和线程的调度是由操作系统自动完成
- 区别:进程:每个进程都有自己独立的内存空间,不同进程之间的内存空间不共享。进程之间的通信有操作系统传递,导致通讯效率低,切换开销大。
- 区别:线程:一个进程可以有多个线程,所有线程共享进程的内存空间,通讯效率高,切换开销小。共享意味着竞争,导致数据不安全,为了保护内存空间的数据安全,引入”互斥锁”。一个线程在访问内存空间的时候,其他线程不允许访问,必须等待之前的线程访问结束,才能使用这个内存空间
- 协程是线程的一种表现形式,协程又称微线程。在单线程上执行的多个任务,用函数切换,开销极小
多线程、协程区别:
多进程的优点:
- 编程相对容易;通常不需要考虑锁和同步资源的问题。
- 更强的容错性:比起多线程的一个好处是一个进程崩溃了不会影响其他进程。
- 有内核保证的隔离:数据和错误隔离。 对于使用如C/C++这些语言编写的本地代码,错误隔离是非常有用的:采用多进程架构的程序一般可以做到一定程度的自恢复;(master守护进程监控所有worker进程,发现进程挂掉后将其重启)
多线程的优点:
多进程应用场景
- nginx主流的工作模式是多进程模式(也支持多线程模型)
- 几乎所有的web server服务器服务都有多进程的,至少有一个守护进程配合一个worker进程,例如apached、httpd等等以d结尾的进程包括init.d本身就是0级总进程,所有你认知的进程都是它的子进程
- chrome浏览器也是多进程方式。 原因:
- 可能存在一些网页不符合编程规范,容易崩溃,采用多进程一个网页崩溃不会影响其他网页;而采用多线程会。
- 网页之间互相隔离,保证安全,不必担心某个网页中的恶意代码会取得存放在其他网页中的敏感信息
- redis也可以归类到“多进程单线程”模型(平时工作是单个进程,涉及到耗时操作如持久化或aof重写时会用到多个进程)
- 多线程应用场景:
- 线程间有数据共享,并且数据是需要修改的(不同任务间需要大量共享数据或频繁通信时)。
- 提供非均质的服务(有优先级任务处理)事件响应有优先级。
- 单任务并行计算,在非CPU Bound的场景下提高响应速度,降低时延。
- 与人有IO交互的应用,良好的用户体验(键盘鼠标的输入,立刻响应)
- 案例:桌面软件,响应用户输入的是一个线程,后台程序处理是另外的线程;memcached
- 多线程的常见应用场景:
- 后台任务,例如:定时向大量(100w以上)的用户发送邮件
- 异步处理,例如:发微博、记录日志等
- 分布式计算
python在多线程处理CPU密集型程序时可以选择多进程实现,有效的利用多核提升效率;而IO密集型的由于99%的时间都花在IO上,花在CPU上的时间很少,所以多线程也能提高很大效率。
如何合理地选择多线程和多进程
需要频繁创建销毁的优先用线程(进程的创建和销毁开销过大)
- 这种原则最常见的应用就是Web服务器了,来一个连接建立一个线程,断了就销毁线程,要是用进程,创建和销毁的代价是很难承受的
- 需要进行大量计算的优先使用线程(CPU频繁切换)
- 所谓大量计算,当然就是要耗费很多CPU,切换频繁了,这种情况下线程是最合适的。
- 这种原则最常见的是图像处理、算法处理。
- 强相关的处理用线程,弱相关的处理用进程
- 一般的Server需要完成如下任务:消息收发、消息处理。“消息收发”和“消息处理”就是弱相关的任务,而“消息处理”里面可能又分为“消息解码”、“业务处理”,这两个任务相对来说相关性就要强多了。因此“消息收发”和“消息处理”可以分进程设计,“消息解码”、“业务处理”可以分线程设计。
-
创建线程方法
继承Thread ```java // 构造方法的参数是给线程指定名字,推荐 Thread t1 = new Thread(“t1”) {
@Override // run 方法内实现了要执行的任务 public void run() {
log.debug("hello");
} };
t1.start();
输出:19:19:00 [t1] c.ThreadStarter - hello
2. **实现Runnable接口**
```java
// 创建任务对象
Runnable task2 = new Runnable() {
@Override
public void run() {
log.debug("hello");
}
};
// 参数1:是任务对象; 参数2:是线程名字,推荐
Thread t2 = new Thread(task2, "t2");
t2.start();
输出:19:19:00 [t2] c.ThreadStarter - hello
- 实现Callable接口 + FutureTask (可以拿到返回结果,可以处理异常)
```java
// 创建任务对象
FutureTask
task3 = new FutureTask<>(() -> { log.debug(“hello”); Thread.sleep(2000); return 100; });
// 参数1:是任务对象; 参数2:是线程名字,推荐 new Thread(task3, “t3”).start();
// 主线程阻塞,同步等待 task 执行完毕的结果 Integer result = task3.get(); log.debug(“结果是:{}”, result);
输出: 19:22:27 [t3] c.ThreadStarter - hello 19:22:27 [main] c.ThreadStarter - 结果是:100
4. **线程池**
![image.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1668911107310-386bfc2e-0da3-40b3-9214-0e0cee6e176b.png#averageHue=%23eef6ef&clientId=uf295d958-292f-4&from=paste&height=365&id=u3a53b3b5&originHeight=542&originWidth=1006&originalType=binary&ratio=1&rotation=0&showTitle=false&size=486150&status=done&style=none&taskId=ufe9411c1-12a2-4b73-9dcc-f6162ff057d&title=&width=678)
<a name="jO02V"></a>
### 线程池
<a name="mPxs5"></a>
#### 线程池的7大参数
![](https://cdn.nlark.com/yuque/0/2021/png/22523384/1635164768461-0f23866b-4b2b-4d6f-8de4-c8100bdf0d1a.png#averageHue=%23eaf3ec&from=url&id=g883y&originHeight=906&originWidth=1238&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)<br />运行流程:
1. 线程池创建,准备好core数量的核心线程,准备接受任务
2. 新的任务进来,用core准备好的空闲线程执行。
1. core满了,就将再进来的任务放入阻塞队列中。空闲的core就会自己去阻塞队列获取任务执行
2. 阻塞队列满了,就直接开新线程执行,最大只能开到max指定的数量
3. max都执行好了。Max-core数量空闲的线程会在keepAliveTime指定的时间后自动销毁。最终保持到core大小
4. 如果线程数开到了 max 的数量,还有新任务进来,就会使用reject指定的拒绝策略进行处理
3. 所有的线程创建都是由指定的factory创建的。
面试:<br />一个线程池 **core** **7**; **max** **20** ,**queue**:**50** ,**100** 并发进来怎么分配的;<br />先有 7 个能直接得到执行,接下来 50 个进入队列排队,在多开 13 个继续执行。现在 70 个 被安排上了。剩下 30 个默认拒绝策略。
<a name="lG6yS"></a>
#### 常见的4种线程池
1. **newCachedThreadPool**
1. 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
2. **newFixedThreadPool**
1. 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
3. **newScheduledThreadPool**
1. 创建一个定长线程池,支持定时及周期性任务执行。
4. **newSingleThreadExecutor**
1. 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
![](https://cdn.nlark.com/yuque/0/2021/png/22523384/1635164840133-cf0cb335-afc8-4ec9-b6bb-ecd51a98fc27.png?x-oss-process=image%2Fresize%2Cw_750%2Climit_0#averageHue=%23f1f6f1&from=url&id=fhP9g&originHeight=224&originWidth=750&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
<a name="Em15Y"></a>
### 线程通信的方式
1. 线程的通信可以被定义为:线程通信就是当多个线程共同操作共享的资源时,互相告知自己的状态以避免资源争夺
2. 线程通信主要可以分为三种方式,分别为共享内存、消息传递和管道流。每种方式有不同的方法来实现
1. **共享内存:**线程之间共享程序的公共状态,线程之间通过读-写内存中的公共状态来隐式通信。
1. volatile共享内存
2. ① 线程A把本地内存A更新过的共享变量刷新到主内存中<br />② 线程B到内存中去读取线程A之前已更新过的共享变量
3. volatile有一个关键的特性:保证内存可见性,即多个线程访问内存中的同一个被volatile关键字修饰的变量时,当某一个线程修改完该变量后,需要先将这个最新修改的值写回到主内存,从而保证下一个读取该变量的线程取得的就是主内存中该数据的最新值,这样就保证线程之间的透明性,便于线程通信
2. **消息传递:**线程之间没有公共的状态,线程之间必须通过明确的发送信息来显示的进行通信。
1. wait/notify等待通知方式:等待通知机制就是将处于等待状态的线程将由其它线程发出通知后重新获取CPU资源,继续执行之前没有执行完的任务。最典型的例子生产者--消费者模式
2. join方式:在当前线程A调用线程B的join()方法后,会让当前线程A阻塞,直到线程B的逻辑执行完成,A线程才会解除阻塞,然后继续执行自己的业务逻辑,这样做可以节省计算机中资源
3. **管道流**
1. 管道输入/输出流的形式:道流是是一种使用比较少的线程间通信方式,管道输入/输出流和普通文件输入/输出流或者网络输出/输出流不同之处在于,它主要用于线程之间的数据传输,传输的媒介为管道
<a name="cKm1G"></a>
### 线程的状态
1. NEW - 新建
2. RUNNABLE - 准备就绪
3. BLOCKED - 阻塞
4. WAITING - 不见不散
5. TIMED_WAITING - 过时不候
6. TERMINATED - 终结
<a name="eX0fP"></a>
### 多线程并发安全问题
1. 线程安全问题:当多个线程下,线程没有按照我们预期的想法执行,导致共享变量出现异常
2. 解决线程安全的方式:原子类(juc包)、volatile关键字、加锁(synchronized锁、lock锁)
3. **原子类**是juc atomic包下的一系列类,通过CAS比较与交换的方式实现共享变量的更新,当期望值与预期值相同时,才对共享变量进行修改
4. **volatile关键字:**保证内存的可见性,不保证原子性,防止指令从排序,保证了【单个变量】读写的线程安全。可见性问题是JMM内存模型中定义每个核心存在一个内存副本导致的,核心只操作他们的内存副本,volatile保证了一旦修改变量则立即刷新到共享内存中,且其他核心的内存副本失效,需要重新读取(以上两种方式都是只保证了单个变量的安全)
5. **加锁:**锁则可以保证临界区内的多个共享变量线程安全。加锁有两种方式(synchronized和lock)synchronized可以加在代码块上,普通方法上,静态方法上,在1.6之后引入轻量级锁、偏向锁等优化。lock锁是通过lock加锁,unlock解锁的方式锁住一段代码,基于AQS实现,其加锁解锁就是操作AQS的state变量
<a name="T7iMK"></a>
### 进程和线程的上下文切换
1. 进程上下文切换:包含了进程执行所需要的所有信息
1. 用户地址空间:包括程序代码,数据,用户堆栈等;
2. 控制信息:进程描述符,内核栈等;
3. 硬件上下文:进程恢复前,必须装入寄存器的数据统称为硬件上下文。
2. 进程切换分为三步骤
1. 切换页目录以使用新的地址空间
2. 切换内核栈
3. 切换硬件上下文
4. 刷新TLB
5. 系统调度器的代码执行
3. **线程上下文切换:**对于linux来说,线程和进程的最大区别就在于地址空间。对于线程切换,第1步是不需要做的,第2和3步是进程和线程切换都要做的。所以明显是进程切换代价大
4. 线程上下文切换和进程上下文切换一个最主要的区别是**线程的切换虚拟内存空间依然是相同的,但是进程切换是不同的**。这两种上下文切换的处理都是通过操作系统内核来完成的。内核的这种切换过程伴随的最显著的性能损耗是将寄存器中的内容切换出。
5. 另外一个隐藏的损耗是上下文的切换会扰乱处理器的缓存机制。简单的说,一旦去切换上下文,处理器中所有已经缓存的内存地址一瞬间都作废了。还有一个显著的区别是当你改变虚拟内存空间的时候,处理的页表缓冲(processor’s Translation Lookaside Buffer (TLB))或者相当的神马东西会被全部刷新,这将导致内存的访问在一段时间内相当的低效。但是在线程的切换中,不会出现这个问题。
<a name="U5Fwz"></a>
### 线程的同步机制
1. 什么是线程同步?
1. 当使用多个线程来访问同一个数据时,非常容易出现线程安全问题(比如多个线程都在操作同一数据导致数据不一致),所以我们用同步机制来解决这些问题。
2. 实现同步机制有两个方法:
1. 同步代码块:synchronized(同一个数据){} 同一个数据:就是N条线程同时访问一个数据。
2. 同步方法:public synchronized 数据返回类型 方法名(){}<br />就是使用synchronized来修饰某个方法,则该方法称为同步方法。对于同步方法而言,无需显示指定同步监视器,同步方法的同步监视器是 this 也就是该对象的本身(这里指的对象本身有点含糊,其实就是调用该同步方法的对象)通过使用同步方法,可非常方便的将某类变成线程安全的类,具有如下特征:
1. 该类的对象可以被多个线程安全的访问。
2. 每个线程调用该对象的任意方法之后,都将得到正确的结果。
3. 每个线程调用该对象的任意方法之后,该对象状态依然保持合理状态。
4. 注:synchronized关键字可以修饰方法,也可以修饰代码块,但不能修饰构造器,属性等。
1. 多线程是解决IO密集型的任务还是解决CPU密集型的任务
2. 开启100个线程,对于同一个变量,初始值为0,做+1的操作,最后输出是多少,答,不确定,给个范围?
<a name="tmTdt"></a>
## 进程
<a name="wfdMs"></a>
### 进程通信方式
1. 管道
2. 消息队列
3. 共享内存
4. 信号量
5. 信号
6. socket
<a name="bvL2F"></a>
### 进程调度方法
1. **非剥夺(非抢占)调度方式:**当一个进程正在处理机上执行时,即使有某个更为重要或者紧迫的进程进入就绪队列,仍然让正在执行的进程继续执行,知道该进程完成或发生某种事件而进入阻塞态时,才把处理机分配给更为重要或紧迫(优先级更高)的进程。其优点是实现简单,系统开销小,适用于大多数批处理系统,但它不能用于分时系统和大多数实时系统
2. **剥夺(抢占)调度方式:**当一个进程正在处理机上执行时,若有某个更为重要或紧迫的进程(优先级更高)的进程需要使用处理机,则立即暂停正在执行的进程,将处理机分配给这个更重要的进程。这种方式对提高系统吞吐率和响应效率都有明显的好处。但抢占也要遵循一定原则
<a name="sF99O"></a>
### 进程之间状态的转换
1. **创建状态:**进程在创建时需要申请一个空白PCB,向其中填写控制和管理进程的信息,完成资源分配。如果创建工作无法完成,比如资源无法满足,就无法被调度运行,把此时进程所处状态称为创建状态。
2. **就绪状态:**进程已经准备好,已经分配到所需资源,只要分配到CPU就能够立即运行。
3. **执行状态:**进程处于就绪状态被调度后,进程进入执行状态
4. **阻塞状态:**正在执行的进程由于某些事件(I/O请求,申请缓存区失败)而暂时无法运行,进程受到阻塞。在满足请求时进入就绪状态等待系统调用
5. **终止状态:**进程结束,或出现错误,或被系统终止,进入终止状态。无法再执行
![image.png](https://cdn.nlark.com/yuque/0/2022/png/22523384/1668915479994-bc5e7ffa-35b1-46bf-9877-617f18f251ac.png#averageHue=%23f8f6f4&clientId=uf295d958-292f-4&from=paste&height=459&id=u50821712&originHeight=459&originWidth=1327&originalType=binary&ratio=1&rotation=0&showTitle=false&size=88081&status=done&style=none&taskId=ue623223a-7e4e-48ad-876f-38466f68724&title=&width=1327)
7. 用户进程和内核进程有什么区别
8. http请求中客户端和服务端也可以看成两个进程,它们是怎么通信的
9. 进程之间的数据共享
<a name="fL9jT"></a>
## 锁
<a name="G5ajX"></a>
### 读写锁,不同点,应用场景
![](https://cdn.nlark.com/yuque/0/2021/png/22523384/1636811826678-386be6ff-5184-4cb9-8cc2-aae33488f07f.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_31%2Ctext_57uP5Y6G5Y2z576O5aW9%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#averageHue=%23fdfbfa&from=url&height=368&id=pwavg&originHeight=577&originWidth=1103&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=&width=703)
```java
表锁:整个表操作,不会发生死锁
行锁:每个表中的单独一行进行加锁,会发生死锁
读锁:共享锁(可以有多个人读),会发生死锁
写锁:独占锁(只能有一个人写),会发生死锁
读写锁:一个资源可以被多个读线程访问,也可以被一个写线程访问,但不能同时存在读写线程,读写互斥,读读共享
读写锁ReentrantReadWriteLock
读锁为ReentrantReadWriteLock.ReadLock,readLock()方法
写锁为ReentrantReadWriteLock.WriteLock,writeLock()方法
创建读写锁对象private ReadWriteLock rwLock = new ReentrantReadWriteLock();
写锁 加锁 rwLock.writeLock().lock();,解锁为rwLock.writeLock().unlock();
读锁 加锁rwLock.readLock().lock();,解锁为rwLock.readLock().unlock();
死锁的四个条件
- 互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
- 请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
- 不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链
预防死锁
多个进程并发执行,由于竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法推进,这就是死锁现象
破坏互斥条件
- 破坏不可剥夺条件: 当进程的新资源不可取得时,释放自己已有的资源,待以后需要时重新申请。 但这种方法可能导致迁移阶段工作的失效,反复地申请和释放资源会增加系统开销,降低系统吞吐量。
- 破坏请求并保持条件:进程在运行前一次申请完它所需要的全部资源,在它的资源为满足前,不把它投入运行。一旦投入运行,这些资源都归它所有,不能被剥夺。但这种方法系统资源被严重浪费,而且可能导致饥饿现象,由于个别进程长时间占用某个资源,导致等待该资源的进程迟迟无法运行。
破坏循环等待条件: 给资源编号,规定每个进程必须按编号递增地顺序请求资源,同类资源一次性申请完。这种方法存在问题是发生作业使用资源地顺序与系统规定的顺序不同,造成系统地浪费,并且给编程带来麻烦
死锁解除
一旦检测出死锁,就应该立即采取措施来接触死锁。死锁解除的主要方法有:
资源剥夺法。 挂起某些死锁进程,抢占它的资源,分配给其他死锁进程。
- 撤销进程法。 强制撤销部分进程并剥夺这些进程的资源,让其他进程顺利执行。
-
进程有哪些锁
表锁:整个表操作,不会发生死锁
- 行锁:每个表中的单独一行进行加锁,会发生死锁
- 悲观锁:单独每个人完成事情的时候,执行上锁解锁。解决并发中的问题,不支持并发操作,只能一个一个操作,效率低
- 乐观锁:每执行一件事情,都会比较数据版本号,谁先提交,谁先提交版本号
- 共享锁(S锁):又称为读锁(可以有多个人读),可以查看但无法修改和删除的一种数据锁。如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排它锁。获准共享锁的事务只能读数据,不能修改数据。共享锁下其它用户可以并发读取,查询数据。但不能修改,增加,删除数据。资源共享
- 排它锁(X锁):又称为写锁、独占锁(只能有一个人写),若事务T对数据对象A加上X锁,则只允许T读取和修改A,其他任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。这就保证了其他事务在T释放A上的锁之前不能再读取和修改A
互斥锁:在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为” 互斥锁” 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
Java中的自旋锁
自旋锁是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态
- 为什么要使用自旋锁
- 多个线程对同一个变量一直使用CAS操作,那么会有大量修改操作,从而产生大量的缓存一致性流量,因为每一次CAS操作都会发出广播通知其他处理器,从而影响程序的性能
- 对比互斥锁和自旋锁:
- 互斥锁:从等待到解锁过程,线程会从block状态变为running状态,过程中有线程上下文的切换,抢占CPU等开销。
- 自旋锁:从等待到解锁过程,线程一直处于running状态,没有上下文的切换
- 虽然自旋锁效率比互斥锁高,但它会存在下面两个问题:
- 自旋锁一直占用CPU,在未获得锁的情况下,一直运行,如果不能在很短的时间内获得锁,会导致CPU效率降低。
- 试图递归地获得自旋锁会引起死锁。递归程序决不能在持有自旋锁时调用它自己,也决不能在递归调用时试图获得相同的自旋锁
由于自旋锁只是在当前线程不停地执行循环体,不进行线程状态的切换,因此响应速度更快。但当线程数不停增加时,性能下降明显,因为每个线程都需要占用CPU时间。如果线程竞争不激烈,并且保持锁的时间很短,则适合使用自旋锁。
synchronized与lock区别
synchronized是一个关键字而lock是一个接口(lock、lockInterruptibly、tryLock、unlock、newCondition)。
- synchronized是隐式的加锁,lock是显示的加锁。
- synchronized可以作用在方法和代码块上,而lock只能作用在代码块上。
- synchronized作用在静态方法上锁的是当前类的class,作用在普通方法上锁的是当前类的对象。
- 在javap反编译成字节码后,synchronized关键字需要有一个代码块进入的点monitorenter,代码块退出和代码块异常的出口点monitorexit。
- synchronized是阻塞式加锁,而lock中的trylock支持非阻塞式加锁。
- synchronized没有超时机制,而lock中的trylcok可以支持超时机制。
- synchronized不可中断,而lock中的lockInterruptibly可中断的获取锁
- ReentrantLock.lockInterruptibly允许在等待时由其它线程调用等待线程的Thread.interrupt方法来中断等待线程的等待而直接返回,这时不用获取锁,而会抛出一个InterruptedException。 ReentrantLock.lock方法不允许Thread.interrupt中断,即使检测到Thread.isInterrupted,一样会继续尝试获取锁,失败则继续休眠。只是在最后获取锁成功后再把当前线程置为interrupted状态,然后再中断线程
- synchronized采用的是monitor对象监视器,lock的底层原理是AQS
- synchronized只有一个同步队列和一个等待队列,而lock有一个同步队列,可以有多个等待队列。
- 同步队列:排队取锁的线程所在的队列。
- 等待队列:调用 wait 方法后,线程会从同步队列转移到等待队列。
- synchronized是非公平锁,而lock可以是公平锁也可以是非公平锁。
- synchronized用object的notify方法进行唤醒,而lock用condition进行唤醒。
-
JUC三大辅助类
JUC 中提供了三种常用的辅助类,通过这些辅助类可以很好的解决线程数量过多时 Lock 锁的频繁操作。这三种辅助类为:
CountDownLatch: 减少计数
- CountDownLatch类可以设置一个计数器,然后通过countDown方法来进行减 1 的操作,使用 await方法等待计数器不大于0,然后继续执行 await 方法之后的语句
- 场景: 6 个同学陆续离开教室后值班同学才可以关门
- CyclicBarrier: 循环栅栏
- 在使用中CyclicBarrier 的构造方法第一个参数是目标障碍数,每次执行CyclicBarrier 一次障碍数会加一,如果达到了目标障碍数,才会执行cyclicBarrier.await()之后的语句。可以将CyclicBarrier 理解为加1 操作
- 场景:集齐7 颗龙珠就可以召唤神龙
- Semaphore: 信号灯
同步异步区别(同步阻塞/同步非阻塞/异步)
什么是系统调用?库函数和系统调用的区别?
- 系统调用是通向操作系统本身的接口,是面向底层硬件的。通过系统调用,可以使得用户态运行的进程与硬件设备(如CPU、磁盘、打印机等)进行交互,是操作系统留给应用程序的一个接口
- 库函数(Library function)是把函数放到库里,供别人使用的一种方式。方法是把一些常用到的函数编完放到一个文件里,供不同的人进行调用。一般放在.lib文件中。库函数调用则是面向应用开发的,库函数可分为两类,一类是C语言标准规定的库函数,一类是编译器特定的库函数
- 区别:
- 库函数是语言或应用程序的一部分,而系统调用是内核提供给应用程序的接口,属于系统的一部分
- 库函数在用户地址空间执行,系统调用是在内核地址空间执行,库函数运行时间属于用户时间,系统调用属于系统时间,库函数开销较小,系统调用开销较大
- 库函数是有缓冲的,系统调用是无缓冲的
- 系统调用依赖于平台,库函数并不依赖
- 堆和栈的区别以及存储模式有什么区别
- 操作系统内存寻址了解吗?
- 虚存与物理内存等相关
- 计算机内存管理的方式
- 什么是Linux用户态和内核态
- 虚拟内存是干嘛的
- 虚拟内存和物理内存有什么样的区别。举个例子,为什么会需要有虚拟内存和物理内存这两个