多线程基础
用户线程和守护线程
用户线程:也叫工作线程,当线程的任务执行完或通知方式结束。
守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束。
- 常见的守护线程:垃圾回收机制。
- 可以通过
线程.setDaemon(true);
来将该线程设置为守护线程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40package com.f.chapter17.daemonthread;
/**
* @author fzy
* @date 2023/7/2 22:10
*/
public class DaemonThread {
public static void main(String[] args) {
MyDaemonThread myDaemonThread = new MyDaemonThread();
//如果希望当main线程结束后,子线程myDaemonThread可以自动退出。
// 则只需将子线程myDaemonThread设置为守护线程即可。
myDaemonThread.setDaemon(true);
myDaemonThread.start();
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("主线程say hi... " + i);
}
}
}
class MyDaemonThread extends Thread {
private int cnt = 1;
public void run() {
while (true) { //无限循环
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("子线程say hello " + (cnt++));
}
}
}
线程的生命周期
从
Java
的Thread
源码来看,线程共有六种状态:1
2
3
4
5
6
7
8public enum State {
NEW, //尚未启动的线程处于此状态。
RUNNABLE, //在Java虚拟机中执行的线程处于此状态。
BLOCKED, //被阻塞等待监视器锁定的线程处于此状态。
WAITING, //正在等待另一个线程执行特定动作的线程处于此状态。
TIMED_WAITING, //正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
TERMINATED; //已退出的线程处于此状态。
}从线程的生命周期来看,线程共有七种状态(
Runnable
状态又被细分为Ready
和Running
状态):
用代码来显示线程的状态:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39package com.f.chapter17.threadstate;
/**
* @author fzy
* @date 2023/7/3 20:34
*/
public class ThreadState {
public static void main(String[] args) {
T t = new T();
System.out.println(t.getName() + " 的状态为:" + t.getState());
t.start();
while (t.getState() != Thread.State.TERMINATED) {
System.out.println(t.getName() + " 的状态为:" + t.getState());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(t.getName() + " 的状态为:" + t.getState());
}
}
class T extends Thread {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("hi " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}输出结果为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32Thread-0 的状态为:NEW
Thread-0 的状态为:RUNNABLE
hi 0
Thread-0 的状态为:TIMED_WAITING
hi 1
Thread-0 的状态为:RUNNABLE
Thread-0 的状态为:TIMED_WAITING
hi 2
Thread-0 的状态为:TIMED_WAITING
Thread-0 的状态为:TIMED_WAITING
hi 3
Thread-0 的状态为:TIMED_WAITING
Thread-0 的状态为:TIMED_WAITING
hi 4
Thread-0 的状态为:TIMED_WAITING
Thread-0 的状态为:TIMED_WAITING
hi 5
Thread-0 的状态为:TIMED_WAITING
Thread-0 的状态为:TIMED_WAITING
hi 6
Thread-0 的状态为:TIMED_WAITING
Thread-0 的状态为:TIMED_WAITING
hi 7
Thread-0 的状态为:TIMED_WAITING
Thread-0 的状态为:TIMED_WAITING
hi 8
Thread-0 的状态为:TIMED_WAITING
Thread-0 的状态为:TIMED_WAITING
hi 9
Thread-0 的状态为:TIMED_WAITING
Thread-0 的状态为:TIMED_WAITING
Thread-0 的状态为:TERMINATED
★★★线程同步机制
在多线程编程中,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
也可以这样理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作。
Synchronized
同步代码块:
1
2
3synchronized (对象) { //得到对象的锁,才能操作同步代码
//需要被同步的代码;
}synchronized
还可以放在方法声明中,表示整个方法为同步方法。1
2
3public synchronized void m (String name) {
//需要被同步的代码;
}
对于一个售票示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43package com.f.chapter17.sellticket;
/**
* @author fzy
* @date 2023/7/2 10:41
*/
public class SellTicket02 {
public static void main(String[] args) {
TicketWindow02 ticketWindow = new TicketWindow02();
Thread thread1 = new Thread(ticketWindow);
Thread thread2 = new Thread(ticketWindow);
Thread thread3 = new Thread(ticketWindow);
System.out.println("目前一共有 " + ticketWindow.getTicketNum() + " 张票。");
thread1.start();
thread2.start();
thread3.start();
}
}
class TicketWindow02 implements Runnable {
private int ticketNum = 100;
public int getTicketNum() {
return ticketNum;
}
public void run() {
while (true) {
if (ticketNum <= 0) {
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票,还剩 " + (--ticketNum) + " 张票。");
}
}
}由于没有进行线程同步,所以会发生“超卖现象”,即输出结果为:
1
2
3
4
5
6......
窗口Thread-0售出一张票,还剩 1 张票。
窗口Thread-1售出一张票,还剩 2 张票。
窗口Thread-1售出一张票,还剩 0 张票。
窗口Thread-0售出一张票,还剩 -1 张票。
窗口Thread-2售出一张票,还剩 -2 张票。
为了保证售票正常,需要使用线程同步技术:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76package com.f.chapter17.sync;
/**
* @author fzy
* @date 2023/7/2 10:41
*/
public class SellTicket02 {
public static void main(String[] args) {
TicketWindow02 ticketWindow = new TicketWindow02();
Thread thread1 = new Thread(ticketWindow);
Thread thread2 = new Thread(ticketWindow);
Thread thread3 = new Thread(ticketWindow);
System.out.println("目前一共有 " + ticketWindow.getTicketNum() + " 张票。");
thread1.start();
thread2.start();
thread3.start();
}
}
//使用 synchronized 实现线程同步
class TicketWindow02 implements Runnable {
private static int ticketNum = 100;
public static int getTicketNum() {
return ticketNum;
}
public synchronized void sell() { //同步方法:在同一个时刻,只能有一个线程来执行 sell 方法。这时锁在this对象上
/*
//也可以用:
//synchronized (对象) { //得到对象的锁,才能操作同步代码
// //需要被同步的代码;
//}
//来实现线程同步
synchronized (this) {
if (ticketNum <= 0) {
System.out.println("售票结束...");
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票,还剩 " + (--ticketNum) + " 张票。");
}
*/
if (ticketNum <= 0) {
System.out.println("售票结束...");
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票,还剩 " + (--ticketNum) + " 张票。");
}
public void run() {
while (true) {
sell();
if (ticketNum <= 0) {
break;
}
}
}
}输出结果就正常了:
1
2
3
4
5
6窗口Thread-2售出一张票,还剩 3 张票。
窗口Thread-2售出一张票,还剩 2 张票。
窗口Thread-0售出一张票,还剩 1 张票。
窗口Thread-2售出一张票,还剩 0 张票。
售票结束...
售票结束...
同步原理
以上面的售票例子为例,分析同步原理。

- 首先
run
方法是一个synchronized
方法,意味着该方法采用线程同步机制。 thread1、thread2、thread3
这三个线程都以“抢夺式”的方式来争抢执行run
方法。- 假设
thread1
争抢成功,则thread1
会取得该ticketWindow
对象的锁,然后开始执行相应的代码内容,此时由于thread2、thread3
并没有该对象的锁,所以无法执行。 - 等到
thread1
执行完代码后,其将锁放回该对象中,然后和thread2、thread3
一起,继续争抢执行run
方法,如此循环往复。
注意:该锁为对象锁,即该锁是在
ticketWindow
对象上,而不是在run
方法上。除了对象锁以外,还有类锁,类锁使用在类的静态方法中。
- 首先
★互斥锁
- 在
Java
语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。 - 每个对象都对应于一个可称为 “互斥锁” 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
- 关键字
synchronized
用来与对象的互斥锁联系。当某个对象用synchronized
修饰时,表明该对象在任一时刻只能由一个线程访问。 - 同步的局限性:导致程序的执行效率要降低。
- 同步方法(非静态的)的锁可以是
this
,也可以是其他对象(要求是同一个对象)。 - 同步方法(静态的)的锁为当前类本身。
- 注意:
- 同步方法如果没有使用
static
修饰:默认锁对象为this
。 - 如果方法使用
static
修饰,默认锁对象为当前类.class
。 - 实现的落地步骤:
- 需要先分析要上锁的代码。
- 选择同步代码块或同步方法(优先选择同步代码块,因为采用同步机制的代码越少,代码执行的效率越高)
- ★★★要求多个线程的锁对象为同一个! -> 很重要!
- 同步方法如果没有使用
线程死锁
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程上是一定要避免死锁的发生。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59package com.f.chapter17.sync;
/**
* @author fzy
* @date 2023/7/4 14:14
* 模拟线程死锁
*/
public class DeadLock {
public static void main(String[] args) {
//这段代码形成死锁
DeadLockDemo deadLockDemo1 = new DeadLockDemo(true);
DeadLockDemo deadLockDemo2 = new DeadLockDemo(false);
deadLockDemo1.start();
deadLockDemo2.start();
//分析:
// 1.首先deadLockDemo1获得了对象o1的对象锁,然后开始休眠1秒
// 2.接着deadLockDemo2获得了对象o2的对象锁
// 3.此时deadLockDemo1仍在休眠,而deadLockDemo2希望得到对象o1的对象锁
// 4.但是对象o1的对象锁在deadLockDemo1,因此deadLockDemo2无法继续向下执行
// 5.然后deadLockDemo1休眠结束,若deadLockDemo1要继续向下执行,就要得到对象o2的对象锁
// 6.但是对象o2的对象锁在deadLockDemo2,因此deadLockDemo1无法继续向下执行
// 7.这就造成一个情况:若deadLockDemo1要继续向下执行,就要得到对象o2的对象锁,否则其无法释放对象o1的对象锁,
// 若deadLockDemo2要继续向下执行,就要得到对象o1的对象锁,否则其无法释放对象o2的对象锁,由此形成死锁。
}
}
class DeadLockDemo extends Thread{
static Object o1 = new Object(); //设置为static,保证多个线程的对象锁为同一个
static Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag) {
this.flag = flag;
}
public void run() {
if (flag) {
synchronized (o1) { //对象互斥锁,下面就是同步代码
System.out.println(Thread.currentThread().getName() + " 进入1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " 进入2");
}
}
} else {
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " 进入3");
synchronized (o1) {
System.out.println(Thread.currentThread().getName() + " 进入4");
}
}
}
}
}
★释放锁
下面的操作会释放锁:
- 当前线程的同步代码块、同步方法执行结束。
- 当前线程在同步代码块、同步方法中遇到
break
、return
。 - 当前线程在同步代码块、同步方法中出现了未处理的
Error
或Exception
,导致异常结束。 - 当前线程在同步代码块、同步方法中执行了有锁的对象的
wait()
方法,导致当前线程暂停,并释放锁。- 注意:在调用
wait()
方法使得线程暂停并释放锁后,后续还要调用notify()
或notifyAll()
方法来将线程唤醒,使得线程能继续执行。
- 注意:在调用
下面的操作不会释放锁:
- 线程执行同步代码块或同步方法时,程序调用
Thread.sleep()
、Thread.yield()
方法暂停当前线程的执行,不会释放锁。 - 线程执行同步代码块时,其他线程调用了该线程的
suspend()
方法将该线程挂起,该线程不会释放锁。
- 注意:应尽量避免使用
suspend()
和resume()
来控制线程,方法不再推荐使用。
- 线程执行同步代码块或同步方法时,程序调用
★wait、notify和notifyAll
wait
、notify
和notifyAll
方法用于允许线程通过访问公共对象来相互通信,或者换句话说,可以将Object
视为通过这些方法进行线程间通信的媒介。这些方法需要从同步上下文中调用,否则会抛出java.lang.IllegalMonitorStateException
异常。wait()
:当您在对象上调用wait
方法时,它会告诉线程释放该对象的锁并进入睡眠状态,除非直到某个其他线程进入同一监视器并在其上调用notify
或notifyAll
方法。notify()
:当您在对象上调用notify
方法时,它会唤醒等待该对象的锁的线程之一。因此,如果多个线程正在等待同一个对象锁,它将唤醒其中一个。现在你一定想知道它会唤醒哪一个。它实际上取决于操作系统的实现。notifyAll()
:notifyAll
将唤醒等待该对象的锁的所有线程,不像notify
只唤醒其中一个。哪个线程首先被唤醒取决于线程优先级和操作系统实现。
通过
wait、notify、notifyAll
方法来解决“线程死锁”小节的死锁问题:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56package com.f.chapter17.sync;
/**
* @author fzy
* @date 2023/7/4 21:20
*/
public class WaitExample {
public static void main(String[] args) {
SolveDeadLockDemo solveDeadLockDemo1 = new SolveDeadLockDemo(true);
SolveDeadLockDemo solveDeadLockDemo2 = new SolveDeadLockDemo(false);
solveDeadLockDemo1.start();
solveDeadLockDemo2.start();
}
}
class SolveDeadLockDemo extends Thread {
static Object o1 = new Object(); //设置为static,保证多个线程的对象锁为同一个
static Object o2 = new Object();
boolean flag;
public SolveDeadLockDemo(boolean flag) {
this.flag = flag;
}
public void run() {
if (flag) {
synchronized (o1) { //对象互斥锁,下面就是同步代码
System.out.println(Thread.currentThread().getName() + " 进入1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//调用wait方法释放o1对象锁,注意wait方法一定是在synchronized中调用
try {
o1.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " 进入2");
}
}
} else {
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " 进入3");
synchronized (o1) {
System.out.println(Thread.currentThread().getName() + " 进入4");
//将释放o1对象锁的线程唤醒,使其继续执行
o1.notify();
}
}
}
}
}