Skip to content

Commit 0091ab3

Browse files
committed
多线程+Spring
1 parent be0a0bf commit 0091ab3

15 files changed

+2376
-0
lines changed

Java/多线程(1)基础.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
2+
3+
4+
5+
## 进程与线程
6+
7+
**进程**:资源分配和调度单位
8+
9+
* 操作系统进行资源分配的最小单位,资源包括:CPU、内存空间、磁盘等
10+
* 同一个进程中的多个线程共享该进程中的全部资源(堆和方法区资源)
11+
* 进程之间是相互独立的
12+
13+
**线程**:线程是CPU调度的最小单位,必须依赖进程而存在
14+
15+
16+
17+
## 线程上下文切换
18+
19+
巧妙地利用了时间片轮转的方式,CPU 给每个任务都服务一定的时间,然后把当前任务的状态保存下来,在加载下一任务的状态后,继续服务下一任务,任务的状态保存及再加载, 这段过程就叫做上下文切换。时间片轮转的方式使多个任务在同一颗 CPU 上执行变成了可能。
20+
21+
**上下文:**是指某一时间点 CPU 寄存器和程序计数器的内容
22+
23+
24+
25+
**引起线程上下文切换的原因**
26+
27+
* 当前执行任务的时间片用完之后,系统 CPU 正常调度下一个任务;
28+
* 当前执行任务碰到 IO 阻塞,调度器将此任务挂起,继续下一任务;
29+
* 多个任务抢占锁资源,当前任务没有抢到锁资源,被调度器挂起,继续下一任务;
30+
* 用户代码挂起当前任务,让出 CPU 时间;
31+
* 硬件中断;
32+
33+
34+
35+
**线程让出 cpu 的情况:**
36+
37+
* 当前运行线程主动放弃 CPU,JVM 暂时放弃 CPU 操作(基于时间片轮转调度的 JVM 操作系统不会让线程永久放弃 CPU,或者说放弃本次时间片的执行权),例如调用 yield()方法。
38+
* 当前运行线程因为某些原因进入阻塞状态,例如阻塞在 I/O 上。
39+
* 当前运行线程结束,即运行完 run()方法里面的任务。
40+
41+
42+
43+
## 线程调度方式
44+
45+
* **抢占式调度:**抢占式调度指的是每条线程执行的时间、线程的切换都由系统控制。在这种机制下,一个线程的堵塞不会导致整个进程堵塞。
46+
* **协同式调度:**协同式调度指某一线程执行完后主动通知系统切换到另一线程上执行,这种模式就像接力赛一样,但它有一个致命弱点:如果一个线程编
47+
48+
写有问题,运行到一半就一直堵塞,那么可能导致整个系统崩溃。
49+
50+
JVM 的线程调度实现(抢占式调度)
51+
52+
## 串行、并行与并发
53+
54+
串行:在同一时间一个人只能做一件事的情况下,一个人交替做着做多件事
55+
56+
并行:在同一时间一个人同时做多件事,如左后开弓一般
57+
58+
并发:在同一时间多个人抢着做同一件事,如竞争一个位置
59+
60+
## 同步和异步
61+
62+
同步:发送一个请求,等待返回,然后再发送下一个请求。
63+
64+
异步:发送一个请求,不等待返回,随时可以在发送下一个请求。
65+
66+
## 吞吐量TPS
67+
68+
吞吐量表示在单位时间内通过某个网络或接口的数据量
69+
70+
## 高并发
71+
72+
优点:充分利用CPU、加快响应用户的时间、是代码模块化、异步化、简单化
73+
74+
缺点:发生线程安全问题、容易死循环
75+
76+
**高并发与分布式**
77+
78+
* **高并发**:指并发在单个资源个体的情况下实现达到最大资源利用价值,如:1个服务器4个CPU*4核,那么并行为16,高并发则利用多线程技术,可能实现160个
79+
* **分布式**:当来了 1 万个并发时,原来资源满足不了,就并行的再多开几个资源服务器。说白了就是多找几个服务器写作完成。常见架构有Hadoop、Hbase、Zookeeper、Redis

Java/多线程(2)Thread.md

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
2+
3+
4+
5+
# 线程实现方式
6+
7+
**线程的实现有 3 种方式,如下:**
8+
9+
* 继承 Thread 类
10+
* 实现 Runnable 接口
11+
* 实现 Callable 接口
12+
13+
## Thread 和Runnable实现
14+
15+
直接示例说明
16+
17+
```java
18+
//1、继承 Thread 类
19+
public class MyThread extends Thread {
20+
public void run() {
21+
System.out.println("MyThread.run()");
22+
}
23+
}
24+
25+
//2、实现 Runnable 接口
26+
public class MyThread implements Runnable {
27+
public void run() {
28+
System.out.println("MyThread.run()");
29+
}
30+
}
31+
32+
// 启动
33+
MyThread myThread = new MyThread();
34+
myThread.start();
35+
36+
```
37+
38+
实现接口比继承 Thread 类具有优势,因为接口是多实现, Thread 类单继承
39+
40+
41+
42+
## Callable 实现
43+
44+
有返回值的任务必须实现 Callable 接口,类似的,无返回值的任务必须 Runnable 接口。执行Callable 任务后,可以获取一个 Future 的对象,在该对象上调用 get 就可以获取到 Callable 任务返回的 Object 了,再结合线程池接口ExecutorService 就可以实现传说中有返回结果的多线程了。
45+
46+
```java
47+
// 创建一个线程池
48+
ExecutorService executor = Executors.newFixedThreadPool(2);
49+
//创建一个Callable,3秒后返回String类型
50+
Callable myCallable = new Callable() {
51+
@Override
52+
public String call() throws Exception {
53+
  Thread.sleep(3000);
54+
  System.out.println("calld方法执行了");
55+
  return "call方法返回值";
56+
}
57+
};
58+
System.out.println("提交任务之前 "+LocalDatetime.now());
59+
Future future = executor.submit(myCallable);
60+
System.out.println("提交任务之后,获取结果之前 "+LocalDatetime.now());
61+
System.out.println("获取返回值: "+future.get());
62+
System.out.println("获取到结果之后 "+ LocalDatetime.now());
63+
// 关闭线程池
64+
pool.shutdown();
65+
66+
输出:
67+
提交任务之前 12:13:01
68+
提交任务之后,获取结果之前 12:13:01
69+
calld方法执行了
70+
获取返回值: call方法返回值
71+
获取到结果之后 12:13:04
72+
```
73+
74+
**get()方法的阻塞性:**在调用submit提交任务之后,主线程运行到future.get()的时,会发生阻塞,一直到任务执行完毕,拿到了返回的返回值,主线程才会继续运行。这里注意一下,他的阻塞性是因为调用get()方法时,任务还没有执行完,所以会一直等到任务完成,形成了阻塞。任务是在调用submit方法时就开始执行了,如果在调用get()方法时,任务已经执行完毕,那么就不会造成阻塞。
75+
76+
**设置超时时间**
77+
78+
```java
79+
future1.get(3, TimeUnit.SECONDS)
80+
```
81+
82+
## Callable和Runnable区别
83+
84+
执行方法不同;Callable的执行体 call() 方法可以返回值和抛出异常 ,通过 Future 对象进行封装
85+
86+
87+
88+
# 线程生命周期(状态)
89+
90+
在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5 种状态。
91+
92+
* **新建状态(NEW)**:当程序使用 new 关键字创建了一个线程之后,该线程就处于新建状态,此时仅由 JVM 为其分配内存,并初始化其成员变量的值
93+
* **就绪状态(RUNNABLE)**:当线程对象调用了 start()方法之后,该线程处于就绪状态。Java 虚拟机会为其创建方法调用栈和程序计数器,等待调度运行。
94+
* **运行状态(RUNNING)**:如果处于就绪状态的线程获得了 CPU,开始执行 run()方法的线程执行体,则该线程处于运行状态。
95+
* **阻塞状态(BLOCKED)**:阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得 cpu timeslice 转到运行(running)状态。阻塞的情况分三种
96+
* **线程死亡(DEAD)**:线程会以三种方式结束,结束后就是死亡状态。
97+
98+
![](多线程(2)Thread/untitled diagram.png)
99+
100+
101+
102+
# 线程方法
103+
104+
## 常见方法
105+
106+
线程相关的基本方法有 Object(wait,notify,notifyAll),Thread(sleep,join,yield,interrupt等)
107+
108+
### wait
109+
110+
线程等待;调用该方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中断才会返回,需要注意的是调用 wait()方法后,**会释放对象的锁**。因此,wait 方法一般用在同步方法或同步代码块中。
111+
112+
### notify
113+
114+
线程唤醒;Object 类中的 notify() 方法,**随机唤醒在此对象监视器上等待的单个线程**
115+
116+
线程通过调用其中一个 wait() 方法,在对象的监视器上等待,直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程,被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。
117+
118+
类似的方法还有 notifyAll() ,唤醒再次监视器上等待的所有线程。
119+
120+
### notifyAll
121+
122+
唤醒在此对象监视器上等待的所有线程
123+
124+
### sleep
125+
126+
线程睡眠;sleep 导致当前线程进行休眠,与 wait 方法不同的是 sleep 不会释放当前占有的锁,sleep(long)会导致线程进入 TIMED-WATING 状态,而 wait()方法会导致当前线程进入 WATING 状态
127+
128+
### join
129+
130+
线程等待, join() 方法,等待其他线程终止,在当前线程中调用一个线程的 join() 方法,则当前线程转为阻塞状态,回到另一个线程结束,当前线程再由阻塞状态变为就绪状态,等待 cpu 的宠幸。
131+
132+
**为什么要用 join()方法:**很多情况下,主线程生成并启动了子线程,需要用到子线程返回的结果,也就是需要主线程需要在子线程结束后再结束,这时候就要用到 join() 方法。
133+
134+
### yield
135+
136+
线程让步;yield() 会使当前线程让出 CPU 执行时间片,然后重新与其他线程一起竞争 CPU 时间片。
137+
138+
一般情况下,优先级高的线程有更大的可能性成功竞争得到 CPU 时间片,但这又不是绝对的,有的操作系统对线程优先级并不敏感。
139+
140+
### interrupt
141+
142+
中断一个线程,其本意是给这个线程一个通知信号,会影响这个线程内部的一个中断标识位。这个线程本身并不会因此而改变状态(如阻塞,终止等)。
143+
144+
**特点**
145+
146+
1. **调用 interrupt()方法并不会中断一个正在运行的线程**。也就是说处于 Running 状态的线程并不会因为被中断而被终止,仅仅改变了内部维护的中断标识位而已。
147+
2. **阻塞状态下使用,会抛异常**:如使用了 sleep,同步锁的 wait,socket 中的 receiver,accept 等方法时,会使线程处于阻塞状态。当调用线程的 interrupt()方法时,会抛出 InterruptException 异常。阻塞中的那个方法抛出这个异常,通过代码捕获该异常
148+
3. **异常是返回 false**:许多声明抛出 InterruptedException 的方法(如 Thread.sleep(long mills 方法)),抛出异常前,都会清除中断标识位,所以抛出异常后,调用 isInterrupted()方法将会返回 false。
149+
4. **修改中断状态**:中断状态是线程固有的一个标识位,可以通过此标识位安全的终止线程。比如,你想终止一个线程 thread 的时候,可以调用 thread.interrupt()方法,在线程的 run 方法内部可以根据 thread.isInterrupted()的值来优雅的终止线程。
150+
151+
## **其他方法**
152+
153+
* sleep():强迫一个线程睡眠N毫秒。
154+
* isAlive(): 判断一个线程是否存活。
155+
* join(): 等待线程终止。
156+
* activeCount(): 程序中活跃的线程数。
157+
* enumerate(): 枚举程序中的线程。
158+
* currentThread(): 得到当前线程。
159+
* isDaemon(): 一个线程是否为守护线程。
160+
* setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束)
161+
* setName(): 为线程设置一个名称。
162+
* wait(): 强迫一个线程等待。
163+
* notify(): 通知一个线程继续运行。
164+
* setPriority(): 设置一个线程的优先级。
165+
* getPriority()::获得一个线程的优先级。
166+
167+
168+
169+
## sleep 与 wait 区别
170+
171+
1. sleep()方法属于 Thread 类中的。而 wait()方法是属于Object 类中的。
172+
2. sleep()方法导致了程序暂停执行指定的时间,让出 cpu 该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
173+
3. 在调用 sleep()方法的过程中,线程不会释放对象锁。
174+
4. 而当调用 wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用 notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
175+
176+
## sleep 和 yield的区别
177+
178+
* sleep():让出CPU,进入睡眠状态,CPU让给其他线程运行机会时不考虑线程的优先级;
179+
* yield():让出CPU,进入就绪状态,CPU只会让给相同优先级或更高优先级的线程以运行的机会;
180+
* sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;
181+
182+
## start 与 run 区别
183+
184+
* start()方法来启动线程,真正实现了多线程运行。这时无需等待 run 方法体代码执行完毕,可以直接继续执行下面的代码。
185+
* 通过调用 Thread 类的 start()方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运行。
186+
* 方法 run()称为线程体,它包含了要执行的这个线程的内容,线程就进入了运行状态,开始运行 run 函数当中的代码。 Run 方法运行结束, 此线程终止。然后 CPU 再调度其它线程。
187+
188+
## notify 和 notifyAll的区别
189+
190+
* notify():只有一个等待线程会被唤醒而且它不能保证哪个线程会被唤醒,这取决于线程调度器
191+
* notifyAll():唤醒等待该锁的所有线程,但是在执行剩余的代码之前,所有被唤醒的线程都将争夺锁定
192+
193+
## Thread.sleep(0)的作用
194+
195+
在线程中,调用sleep(0)可以释放cpu时间,让线程马上重新回到就绪队列而非等待队列,sleep(0)释放当前线程所剩余的时间片(如果有剩余的话),这样可以让操作系统切换其他线程来执行,提升效率。
196+
197+
## wait方法和notify/notifyAll方法在放弃对象监视器时有什么区别?
198+
199+
* wait()方法立即释放对象监视器;
200+
* notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃对象监视器
201+
202+
## 为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?
203+
204+
* 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。
205+
206+
207+
208+
# 阻塞的三种情况
209+
210+
* **等待阻塞(o.wait->等待队列):**运行(running)的线程执行 o.wait()方法,JVM 会把该线程放入等待队列(waitting queue)中。
211+
* **同步阻塞(lock->锁池):**运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池(lock pool)中。
212+
* **其他阻塞(sleep/join):**运行(running)的线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O处理完毕时,线程重新转入可运行(runnable)状态。
213+
214+
215+
216+
# 线程的三种结束方式
217+
218+
1. **正常结束**:1. run()或 call()方法执行完成,线程正常结束。
219+
2. **异常结束**:2. 线程抛出一个未捕获的 Exception 或 Error。
220+
3. **调用 stop**:3. 直接调用该线程的 stop()方法来结束该线程—该方法通常容易导致死锁,不推荐使用。
221+
222+
![](多线程(2)Thread/clipboard.png)
223+
224+
225+
226+
# 中断线程的4种方式
227+
228+
1. 正常运行结束
229+
2. 使用自定义的退出标志退出线程,如使用 volatile 的变量终止循环等
230+
3. 使用 interrupt()方法来中断线程,有两种情况
231+
4. 直接使用 thread.stop()来强行终止线程,**(线程不安全),**调用thread.stop()后导致了该线程所持有的所有锁的突然释放(不可控制),
232+
233+
234+
235+
**interrupt()方法来中断线程,有两种情况**
236+
237+
1. **线程处于阻塞状态**:如使用了 sleep,同步锁的 wait,socket 中的 receiver,accept 等方法时,会使线程处于阻塞状态。当调用线程的 interrupt()方法时,会抛出 InterruptException 异常。阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然后 break 跳出循环状态,从而让我们有机会结束这个线程的执行。通常很多人认为只要调用 interrupt 方法线程就会结束,实际上是错的, 一定要先捕获 InterruptedException 异常之后通过 break 来跳出循环,才能正常结束 run 方法。
238+
2. **线程未处于阻塞状态**:使用 isInterrupted()判断线程的中断标志来退出循环。当使用interrupt()方法时,中断标志就会置 true,和使用自定义的标志来控制循环是一样的道理。
239+
240+
```java
241+
public class ThreadSafe extends Thread {
242+
public void run() {
243+
while (!isInterrupted()){ //非阻塞过程中通过判断中断标志来退出
244+
try{
245+
Thread.sleep(5*1000);//阻塞过程捕获中断异常来退出
246+
}catch(InterruptedException e){
247+
e.printStackTrace();
248+
break;//捕获到异常之后,执行 break 跳出循环
249+
}
250+
}
251+
}
252+
}
253+
```
254+
255+
**Thread类提供三个中断方法**
256+
257+
1. **interrupt()**:中断线程 ,设置中断状态为true
258+
2. **interrupted()** : 判断当前线程是否已经中断,**会清空中断标识位**,即重新设置为false。如果连续两次调用该方法,则第二次调用返回false
259+
3. **isInterrupted()**:判断当前线程是否已经中断,不会清除线程对象的中断标识位
260+
261+
262+
263+
# 守护线程
264+
265+
守护线程--也称“服务线程”,他是后台线程,它有一个特性,即为用户线程 提供 公共服务,在没有用户线程可服务时会自动离开。
266+
267+
在 Daemon 线程中产生的新线程也是 Daemon 的。
268+
269+
线程则是 JVM 级别的,以 Tomcat 为例,如果你在 Web 应用中启动一个线程,这个线程的生命周期并不会和 Web 应用程序保持同步。也就是说,即使你停止了 Web 应用,这个线程依旧是活跃的。
270+
271+
**优先级**
272+
273+
守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务。
274+
275+
**设置**
276+
277+
通过 setDaemon(true)来设置线程为“守护线程”;将一个用户线程设置为守护线程的方式是在 线程对象创建 之前 用线程对象的 setDaemon 方法。
278+
279+
280+
281+
**举例**:
282+
283+
垃圾回收线程就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是 JVM 上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。
284+
285+
**生命周期**
286+
287+
守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。也就是说守护线程不依赖于终端,但是依赖于系统,与系统“同生共死”。当 JVM 中所有的线程都是守护线程的时候,JVM 就可以退出了;如果还有一个或以上的非守护线程则 JVM 不会退出。
61.2 KB
Loading
46.5 KB
Loading

0 commit comments

Comments
 (0)