# JUC 并发编程
# 1. JUC 介绍
JUC 就是工具包 java.util.concurrent
# 2. 进程和线程
进程就是一个程序的集合,一个进程往往可以包含多个线程,至少包含一个
Java 默认有两个线程,一个 Main 线程,一个 GC 线程
Java 中在开启线程时调用 start 方法,start 方法调用 start0 方法,start0 是加了 native 的本地方法,底层使用 C++ 调用内存创建线程
public class XianCheng {
public static void main(String[] args) {
A a = new A();
Thread thread = new Thread(a);
thread.start();
}
static class A implements Runnable {
@Override
public void run() {
System.out.println("A 线程");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */ group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */ }
}
}
private native void start0();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 3. 并发和并行
并发指多线程操作同一个资源,CPU 只有一个核时,如果有多条线程就是并发执行,CPU 在多个线程中反复横跳的执行,并发编程的本质就是充分利用 CPU 的资源
并行指多个线程一起执行,CPU 有多个核时,一个核可以处理一个线程,多个和就可以同时处理多个线程,这就是并行执行
如何判断 CPU 的核数:
System.out.println(Runtime.getRuntime().availableProcessors());
# 4. 线程 6 大状态
- new:尚未启动的线程处于此状态,新建
- runnable:在 Java 虚拟机中执行的线程处于此状态,准备就绪
- blocked:被阻塞等待监视器锁定的线程处于此状态,阻塞
- waiting:正在等待另一个线程执行特定动作的线程处于此状态,不见不散
- timed_waiting:正在等待另一个线程执行动作达到指定等待时间的线程处于此状态,过期不候
- terminated:已退出的线程处于此状态,终结
# 5. wait 和 sleep 的区别
- 来自不同的类
wait 来自 Object 类,sleep 来自 Thread 类
- 关于锁的释放
wait 会释放锁;sleep 睡着了会抱着锁睡觉,不会释放锁
- 使用的范围不同
wait 必须在同步代码块中;sleep 可以在任何地方睡
- 是否需要捕获异常
wait 不需要捕获异常;sleep 必须要捕获异常
# 6. Lock 锁
先使用传统的同步锁 Synchronized
public class SynchronizedDemo {
public static void main(String[] args) {
//并发:多线程操作同一个资源类,把资源类丢入线程
SellTickets sellTickets = new SellTickets();
//这里的 Thread 传入两个值,一个是 Runnable 接口,一个是线程名,因为这里 Runnable 是函数式接口所以用的 lambda 表达式
new Thread(() -> {
for(int i = 1; i < 40; i++) {
sellTickets.sell();
}
}, "A").start();
new Thread(() -> {
for(int i = 1; i < 40; i++) {
sellTickets.sell();
}
}, "B").start();
new Thread(() -> {
for(int i = 1; i < 40; i++) {
sellTickets.sell();
}
}, "C").start();
}
}
//资源类
class SellTickets {
private int number = 30;
public synchronized void sell() {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出 1 张票 " + "剩余: " + --number);
}
}
}
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
使用 Lock 锁
Lock 锁语法:
//创建锁
Lock l = ....;
//加锁
l.lock();
try{
//业务代码
} finally {
//一定要解锁,否则会造成死锁
l.unlock();
}
2
3
4
5
6
7
8
9
10
Lock 锁的实现类:
创建可重入锁其实创建的是非公平锁或者是公平锁:
- 公平锁:十分公平,先来的先执行
- 非公平锁:十分不公平,可以插队,模式是非公平锁
使用 Lock 锁实现卖票
public class LockDemo {
public static void main(String[] args) {
SellTickets2 sellTickets2 = new SellTickets2();
new Thread(() -> {
for(int i = 1; i < 40; i++) {
sellTickets2.sell();
}
}, "A").start();
new Thread(() -> {
for(int i = 1; i < 40; i++) {
sellTickets2.sell();
}
}, "B").start();
new Thread(() -> {
for(int i = 1; i < 40; i++) {
sellTickets2.sell();
}
}, "C").start();
}
}
class SellTickets2 {
private int number = 30;
//创建锁
ReentrantLock lock = new ReentrantLock();
public void sell() {
//加锁
lock.lock();
try {
//业务代码
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出 1 张票 " + "剩余: " + --number);
}
} finally {
//解锁
lock.unlock();
}
}
}
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
Synchronized 和 Lock 的区别
- Synchronized 是 Java 内置的关键字;Lock 是一个 Java 类
- Synchronized 无法判断获取锁的状态;Lock 可以判断是否获取到了锁
- Synchronized 会自动释放锁;Lock 必须要手动释放锁,如果不释放锁会死锁
- Synchronized 有线程 1 获得了锁,那么线程 2 就会傻傻的等;Lock 锁就不一定会等待下去
- Synchronized 可重入锁,不可以中断,非公平锁;Lock 可重入锁,可以判断锁,默认是非公平锁
- Synchronized 适合锁少量的代码的同步问题;Lock 适合锁大量的同步代码
# 7. 生产者和消费者问题(线程间通信问题)
- 生产者和消费者问题 Synchronized 版
/**
* @Author: 止束
* @Version: 1.0
* @DateTime: 2025/3/31 1:00
* @Description:
* 线程之间的通信问题:生产者和消费者问题,等待唤醒、通知唤醒
* 需求:线程交替执行,A B 操作同一个变量
* A:num + 1
* B:num - 1
*/public class SynchronizedDemo {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
data.addition();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "A").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
data.subtraction();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "B").start();
}
}
class Data {
private int num = 0;
public synchronized void addition() throws InterruptedException { //对 num + 1,如果 num = 0 就加 1
if (num != 0) {
//如果 num != 0,说明现在等于 1,所以等待,等待 B 线程把 num - 1
this.wait();
}
//num + 1
num++;
System.out.println(Thread.currentThread().getName() + " +1");
//执行完 +1 操作后通知其它线程
this.notifyAll();
}
public synchronized void subtraction() throws InterruptedException {
if (num == 0) {
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName() + " -1");
this.notifyAll();
}
}
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
(1)这里会存在虚假唤醒问题
虚假唤醒问题:
/**
* @Author: 止束
* @Version: 1.0
* @DateTime: 2025/3/31 1:00
* @Description:
* 线程之间的通信问题:生产者和消费者问题,等待唤醒、通知唤醒
* 需求:线程交替执行,A B 操作同一个变量
* A:num + 1
* B:num - 1
* C:num + 1
* D:num - 1
* 这里会造成虚假唤醒现象
*/
public class SynchronizedDemo {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
data.addition();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "A").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
data.subtraction();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "B").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
data.addition();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "C").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
data.subtraction();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "D").start();
}
}
class Data {
private int num = 0;
public synchronized void addition() throws InterruptedException { //对 num + 1,如果 num = 0 就加 1
if (num != 0) {
//如果 num != 0,说明现在等于 1,所以等待,等待 B 线程把 num - 1
this.wait();
}
//num + 1
num++;
System.out.println(Thread.currentThread().getName() + " +1" + " = " + num);
//执行完 +1 操作后通知其它线程
this.notifyAll();
}
public synchronized void subtraction() throws InterruptedException {
if (num == 0) {
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName() + " -1" + " = " + num);
this.notifyAll();
}
}
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
(2)解决虚假唤醒
线程可以唤醒,而不会被通知,即所谓的虚假唤醒,等待应该总是出现在循环中
所以要把 wait 等待写到循环中,所以把 if 改成 while 来解决虚假唤醒
/**
* @Author: 止束
* @Version: 1.0
* @DateTime: 2025/3/31 1:00
* @Description:
* 线程之间的通信问题:生产者和消费者问题,等待唤醒、通知唤醒
* 需求:线程交替执行,A B 操作同一个变量
* A:num + 1
* B:num - 1
* C:num + 1
* D:num - 1
* 这里会造成虚假唤醒现象,把 wait 等待放到循环中,这里把 if 改为 while
*/
public class SynchronizedDemo {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
data.addition();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "A").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
data.subtraction();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "B").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
data.addition();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "C").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
data.subtraction();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "D").start();
}
}
class Data {
private int num = 0;
//对 num + 1,如果 num = 0 就加 1
public synchronized void addition() throws InterruptedException {
while (num != 0) {
//如果 num != 0,说明现在等于 1,所以等待,等待 B 线程把 num - 1
this.wait();
}
//num + 1
num++;
System.out.println(Thread.currentThread().getName() + " +1" + " = " + num);
//执行完 +1 操作后通知其它线程
this.notifyAll();
}
public synchronized void subtraction() throws InterruptedException {
while (num == 0) {
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName() + " -1" + " = " + num);
this.notifyAll();
}
}
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
- JUC 版的生产者和消费者问题
(1)JUC 版的锁换成 Lock 锁,Condition取代了对象监视器方法的使用。
public class JUCDemo {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.addition();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.subtraction();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.addition();
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.subtraction();
}
}, "D").start();
}
}
class Data2 {
private int num = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//对 num + 1,如果 num = 0 就加 1
public void addition(){
lock.lock();
try {
while (num != 0) {
//如果 num != 0,说明现在等于 1,所以等待,等待 B 线程把 num - 1
condition.await();
}
//num + 1
num++;
System.out.println(Thread.currentThread().getName() + " +1" + " = " + num);
//执行完 +1 操作后通知其它线程
condition.signalAll();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
public void subtraction(){
lock.lock();
try {
while (num == 0) {
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName() + " -1" + " = " + num);
condition.signalAll();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
}
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
76
77
78
79
80
81
82
83
84
85
(2)使用 Condition 实现精准的通知和唤醒线程
上面的代码其中不是严格按照 ABCD 的顺序执行的,我们想要按照顺序执行,就要用到精准通知
# 8. 判断锁的是谁
public class LockWho {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.sendSms();
}, "A").start();
new Thread(() -> {
phone.call();
}, "B").start();
}
}
class Phone {
/**
* Synchronized 锁的对象是方法的调用者,两个方法是同一个对象调用的,所以两个方法用的是同一个锁
* 谁先拿到谁执行,如果 A 先拿到,那么 B 会等待 4 秒后 A 释放锁后,B 拿到锁执行
*/
public synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}
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
public class LockWho {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.sendSms();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(3); //让 A 先执行
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(() -> {
phone.hello();
}, "B").start();
}
}
class Phone {
public synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("发短信");
}
/**
* 普通方法不受锁的影响
* 所以就算是 A 线程先执行,它在休眠的 4 秒内 hello 还是会照样输出
*/
public void hello() {
System.out.println("hello");
}
}
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
public class LockWho {
public static void main(String[] args) {
/**
* 两个对象,两个调用对象,两把锁,两者互不干扰
* 就算让 A 先执行,A 在休眠的时候 B 照样输出
*/
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
phone1.sendSms();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(2); //让 A 先执行
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(() -> {
phone2.call();
}, "B").start();
}
}
class Phone {
public synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}
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
public class LockWho {
public static void main(String[] args) {
/**
* 这里只有一个对象,因为这里的 synchronized 锁的是 class,所以两个线程是同一把锁
*/
Phone phone = new Phone();
new Thread(() -> {
phone.sendSms();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(2); //让 A 线程先执行
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(() -> {
phone.call();
}, "B").start();
}
}
class Phone {
/**
* 这里是两个 static 的同步方法,因为 static 关键字修饰静态方法,静态方法在类加载的时候就有了
* 所以这里锁的是 Class 对象,是全局唯一的,因为这两个方法都用 static 修饰了,所以是同一把锁
*/
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("发短信");
}
public static synchronized void call() {
System.out.println("打电话");
}
}
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
public class LockWho {
public static void main(String[] args) {
/**
* 这里的 synchronized 锁的是 class,只要是 Phone 类的都是一把锁
*/
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
phone1.sendSms();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(2); //让 A 线程先执行
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(() -> {
phone2.call();
}, "B").start();
}
}
class Phone {
/**
* 这里是两个 static 的同步方法,因为 static 关键字修饰静态方法,静态方法在类加载的时候就有了
* 所以这里锁的是 class
*/
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("发短信");
}
public static synchronized void call() {
System.out.println("打电话");
}
}
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
public class LockWho {
public static void main(String[] args) {
/**
* 一个 static 的同步方法,一个普通的同步方法,一个对象
*/
Phone phone = new Phone();
new Thread(() -> {
phone.sendSms();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(2); //让 A 线程先执行
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(() -> {
phone.call();
}, "B").start();
}
}
class Phone {
/**
* 这里是一个 static 的同步方法,一个是普通的同步方法
*/
/**
* 这里因为有 static 所以锁的是 Class
*/
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("发短信");
}
/**
* 这里是普通的同步方法,所以锁的是引用该方法的对象,所以两者不是同一把锁
*/
public synchronized void call() {
System.out.println("打电话");
}
}
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
public class LockWho {
public static void main(String[] args) {
/**
* 一个 static 的同步方法,一个普通的同步方法,两个对象
*/
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
phone1.sendSms();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(2); //让 A 线程先执行
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(() -> {
phone2.call();
}, "B").start();
}
}
class Phone {
/**
* 这里是一个 static 的同步方法,一个是普通的同步方法
*/
/**
* 这里因为有 static 所以锁的是 Class
*/
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("发短信");
}
/**
* 这里是普通的同步方法,所以锁的是引用该方法的对象,所以两者不是同一把锁
*/
public synchronized void call() {
System.out.println("打电话");
}
}
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
# 9. 不安全的集合类
- List 不安全
public class UnsafeList {
public static void main(String[] args) {
/**
* 高并发下 ArrayList 是不安全的,当只有一个线程操作时可以使用 ArrayList
* 多线程下报错:java.util.ConcurrentModificationException
*/
List list = new ArrayList();
for(int i = 1; i <= 10; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
改进:
public class UnsafeList {
public static void main(String[] args) {
/**
* 高并发下 ArrayList 是不安全的,当只有一个线程操作时可以使用 ArrayList
* 多线程下报错:java.util.ConcurrentModificationException
*/
//List list = new ArrayList();
/**
* 改进一:并发下 Vector 是线程安全的,所以可以使用 Vector
*/
//List list = new Vector();
/**
* 改进二:把 ArrayList 变成 Synchronized 的
*/
//List list = Collections.synchronizedList(new ArrayList());
/**
* 改进三:使用 CopyOnWriteArrayList
* CopyOnWrite 即写入时复制,是计算机程序设计领域的一种优化策略
*/
List list = new CopyOnWriteArrayList();
for(int i = 1; i <= 10; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
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
- Set 不安全
public class UnsafeSet {
public static void main(String[] args) {
/**
* HashSet 是线程不安全的,java.util.ConcurrentModificationException
*/
//HashSet hashSet = new HashSet();
/**
* 改进一:给 HashSet 加锁
*/
//Set hashSet = Collections.synchronizedSet(new HashSet());
/**
* 改进二:使用 CopyOnWriteArraySet
*/
Set hashSet = new CopyOnWriteArraySet();
for(int i = 1; i <= 30; i++) {
new Thread(() -> {
hashSet.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(hashSet);
}, String.valueOf(i)).start();
}
}
}
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
- 不安全的 Map
public class UnsafeMap {
public static void main(String[] args) {
/**
* HashMap 是不安全的 java.util.ConcurrentModificationException
*/
//HashMap<String, String> map = new HashMap<>();
/**
* 改进一:使用 ConcurrentHashMap
*/
Map<String, String> map = new ConcurrentHashMap<>();
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
System.out.println(map);
}, String.valueOf(i)).start();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 10. Callable
- Callable 可以有返回值
- Callable 可以抛出异常
- Callable 需要实现的是 call() 方法
public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread thread = new MyThread();
FutureTask futureTask = new FutureTask(thread);
new Thread(futureTask, "A").start();
new Thread(futureTask, "B").start();
Integer o = (Integer) futureTask.get();
System.out.println(o);
}
}
class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("call()");
return 1024;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 11. 常用的辅助类
- CountDownLatch
CountDownLatch 是一个同步工具类,它通过一个计数器来实现,初始值为线程的数量,每当一个线程完成了自己的任务,计数器的值就相应的减 1,当计数器达到 0 时,表示所有的线程都已经执行完毕,然后在等待的线程就可以恢复执行任务
应用场景:
- 某个线程需要在其它 n 个线程执行完毕后再向下执行
- 多个线程并行执行同一个任务,提高响应速度
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
/**
* 这里会有 7 个线程,其中 6 个是循环开启的,还有一个是 Main 线程,Main 线程打印 close Door,如果不用 CountDownLatch,那么 Main 线程和那 6 个线程是互不干扰的,Main 线程随时执行,随时打印 close Door,现在我们希望先执行完 6 个线程再执行 Main 线程,可以使用 CountDownLatch
*/
for(int i = 1; i <= 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "执行");
}, String.valueOf(i)).start();
}
System.out.println("close Door");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
使用 CountDownLatch
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//总数是 6,如果有必须要先执行的任务的时候使用
CountDownLatch countDownLatch = new CountDownLatch(6);
for(int i = 1; i <= 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "执行");
countDownLatch.countDown(); //数量 - 1
}, String.valueOf(i)).start();
}
countDownLatch.await(); //等待计数器归零,归零后再向下执行
System.out.println("close Door");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
每次有线程调用 countDown() 数量 -1,如果计数器变为 0,countDownLatch.await()
就会被唤醒,继续执行
- CyclicBarrier
CyclicBarrier 循环栅栏,利用 CyclicBarrier 类可以实现一组线程相互等待,当所有线程都到达某个屏障点后再进行后续的操作。
举个例子:小明、小美、小华、小李四人聚餐,他们每个人到达聚餐地方的时间不一样,有的人早到,有的人晚到,但是他们要都到了以后才能决定点哪些菜。其中的每个人就相当于每个线程,餐厅就是 CyclicBarrier
CyclicBarrier 可以使一定数量的线程反复地在栅栏位置处汇集,当线程到达栅栏位置时将调用 await 方法,这个方法将阻塞直到所有线程都到达栅栏位置,如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有的线程都将被释放,而栅栏将被重置以便下次使用
public class CyclicBarrierDemo {
public static void main(String[] args) {
/**
* 需求:只有集齐了 7 个线程之后才输出 召唤神龙成功
*/
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("召唤神龙成功");
});
for (int i = 1; i <= 7; i++) {
int finalI = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "收集 " + finalI + " 个龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
}
}).start();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- Semaphore
Semaphore(信号量):是一种计数器,用来保护一个或者多个共享资源的访问,如果线程要访问一个资源就必须先获得信号量,如果信号量内部计数器大于 0,信号量 -1,然后允许共享这个资源;否则,如果信号量的计数器等于 0,信号量将会把线程置入休眠直至计数器大于 0。当信号量使用完时,必须释放
public class SemaphoreDemo {
public static void main(String[] args) {
/**
* 场景:抢停车位,3 个停车位 6 个车抢,其中 3 个车抢到停车位之后剩下的 3 个车必须等到它们走了之后才能抢到停车位
*/
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
try {
// acquire() 得到一个信号量
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 抢到车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + " 离开车位");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
semaphore.release(); // 不用了之后就用 release() 释放信号量
}
}, String.valueOf(i)).start();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
semaphore.acquire()
获得 1 个信号量,如果已经满了,等待,直到有信号量
semaphore.release()
释放,会将当前的信号量释放并 +1,然后唤醒等待的线程
作用:多个共享资源互斥的使用,并发限流,控制最大的线程数
# 12. 读写锁
读写锁:读操作可以被多线程同时读,写操作只能有一个线程去写
写锁也叫独占锁,读锁也叫共享锁
如果不加读写锁,会同时有多个线程写,多个线程读
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
//写操作
for (int i = 1; i <= 5; i++) {
int finalI = i;
new Thread(() -> {
myCache.put(finalI + "", finalI + "");
}, String.valueOf(i)).start();
}
//读操作
for (int i = 1; i <= 5; i++) {
int finalI = i;
new Thread(() -> {
myCache.get(finalI + "");
}, String.valueOf(i)).start();
}
}
}
//定义线程资源
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
//写操作
public void put(String key, Object value) {
System.out.println(Thread.currentThread().getName() + " 写入 " + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + " 写入成功");
}
//读操作
public void get(String key) {
System.out.println(Thread.currentThread().getName() + " 读取 " + key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + " 读取成功");
}
}
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
加入读写锁
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCacheLock myCache = new MyCacheLock();
//写操作
for (int i = 1; i <= 5; i++) {
int finalI = i;
new Thread(() -> {
myCache.put(finalI + "", finalI + "");
}, String.valueOf(i)).start();
}
//读操作
for (int i = 1; i <= 5; i++) {
int finalI = i;
new Thread(() -> {
myCache.get(finalI + "");
}, String.valueOf(i)).start();
}
}
}
//定义线程资源
class MyCacheLock {
private volatile Map<String, Object> map = new HashMap<>();
//读写锁
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//写操作,只希望同时只有一个线程写
public void put(String key, Object value) {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " 写入 " + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + " 写入成功");
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
readWriteLock.writeLock().unlock();
}
}
//读操作
public void get(String key) {
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " 读取 " + key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + " 读取成功");
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
readWriteLock.readLock().unlock();
}
}
}
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
# 13. 阻塞队列和同步队列
什么情况下会使用阻塞队列?多线程并发处理,线程池
学习队列的使用:添加到队列、从队列中移除
方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add() | offer() | put() | offer(, ,) |
移除 | remove() | poll() | take() | poll(,) |
查找队首元素 | element() | peek() | - | - |
第一组:抛出异常
/**
* 抛出异常
*/
public class Demo1 {
public static void main(String[] args) {
//队列的大小
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
/**
* 因为已经加满了,如果再加就会抛出异常
* java.lang.IllegalStateException: Queue full
*/
System.out.println(blockingQueue.add("d"));
System.out.println("=================");
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
/**
* 因为已经空了,所以再移除就会抛出异常
* java.util.NoSuchElementException
*/
System.out.println(blockingQueue.remove());
}
}
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
第二组:有返回值,没有异常
/**
* 有返回值,没有异常
*/
public class Demo1 {
public static void main(String[] args) {
//队列的大小
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
/**
* 因为已经加满了,offer 不会报异常,并且有返回值,返回 false
*/
System.out.println(blockingQueue.offer("d"));
System.out.println("=================");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
/**
* 因为已经空了,poll 不会报异常,并且有返回值,返回 null
*/
System.out.println(blockingQueue.poll());
}
}
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
第三组:阻塞等待,一直等待
/**
* 阻塞等待,一直等待
*/
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
//队列的大小
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
/**
* 因为已经加满了,再 put 就会一直等待
*/
blockingQueue.put("d");
System.out.println("=================");
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
/**
* 因为已经空了,再 take 就会一直等待
*/
System.out.println(blockingQueue.take());
}
}
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
第四组:阻塞等待,但是是超时等待,超时了之后就不等了
/**
* 阻塞等待,但是是超时等待,超时了之后就不等了
*/
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
//队列的大小
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
/**
* 因为已经加满了,再 put 就会超时等待
*/
blockingQueue.offer("d", 2, TimeUnit.SECONDS); //等待 2 秒之后就不等了
System.out.println("=================");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
/**
* 因为已经空了,再 take 就会超时等待
*/
blockingQueue.poll(2, TimeUnit.SECONDS); //等待 2 秒之后就不等了
}
}
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
SynchronousQueue 同步队列
SynchronousQueue 同步队列没有容量,进去一个元素,必须等待取出来之后才能再往里面放一个元素,使用 put 和 take
public class Demo2 {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " put 1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName() + " put 2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName() + " put 3");
blockingQueue.put("3");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "T1").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + " 取出 " + blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + " 取出 " + blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + " 取出 " + blockingQueue.take());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "T2").start();
}
}
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
# 14. 线程池
线程池:三大方法、七大参数、四种拒绝策略
- 为什么需要线程池
程序的运行需要占用系统的资源,那么就可以使用池化技术优化资源的使用
线程池可以事先准备一些资源,有人要用,就来池里拿,用完之后再还回去
线程池的好处:
- 降低资源的消耗
- 提高响应的速度
- 方便管理
- 线程复用,可以控制最大并发数,管理线程
- 线程池三大方法
Executors 工具类中有三大方法:
Executors.newSingleThreadExecutor()
:创建单个线程的线程池Executors.newFixedThreadPool(5)
:创建一个固定的线程池的大小Executors.newCachedThreadPool()
:可伸缩的,想大就大,想小就小
第一个方法:
public class Demo {
public static void main(String[] args) {
/**
* 演示 Executors 工具类的三大方法
*/
ExecutorService threadPool = Executors.newSingleThreadExecutor(); //创建单个线程的线程池
try {
for (int i = 0; i < 100; i++) {
//使用了线程池之后,使用线程池来创建线程
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 创建成功");
});
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
//线程池用完后程序结束要关闭线程池
threadPool.shutdown();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
第二个方法:
public class Demo {
public static void main(String[] args) {
/**
* 演示 Executors 工具类的三大方法
*/
ExecutorService threadPool = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
try {
for (int i = 0; i < 100; i++) {
//使用了线程池之后,使用线程池来创建线程
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 创建成功");
});
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
//线程池用完后程序结束要关闭线程池
threadPool.shutdown();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
第三个方法:
public class Demo {
public static void main(String[] args) {
/**
* 演示 Executors 工具类的三大方法
*/
ExecutorService threadPool = Executors.newCachedThreadPool(); //可伸缩的
try {
for (int i = 0; i < 100; i++) {
//使用了线程池之后,使用线程池来创建线程
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 创建成功");
});
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
//线程池用完后程序结束要关闭线程池
threadPool.shutdown();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
但是从阿里巴巴开发规范手册得知,线程池不允许使用 Executors 去创建,而是使用底层的 ThreadPoolExecutor 的方式,这样的处理方式可以自定义线程池的运行规则,规避资源耗尽的风险
看 Executors.newCachedThreadPool()
方法的源码:
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
2
3
4
5
6
它的最大核心线程池大小是 Integer 的最大值,约为 21 亿,这样容易造成 OOM,即内存泄漏
- 线程池的 7 大参数
通过源码分析得知:Executors 工具类中的三大方法其实调用的是 ThreadPoolExecutor 方法
而 ThreadPoolExecutor 就有线程池的 7 大参数,ThreadPoolExecutor 源码如下:
public ThreadPoolExecutor(int corePoolSize, //核心线程池大小
int maximumPoolSize, //最大核心线程池大小
long keepAliveTime, //超时了没有人调用就会释放
TimeUnit unit, //超时时间单位
BlockingQueue<Runnable> workQueue, //阻塞队列
ThreadFactory threadFactory, //线程工厂:创建线程的,一般不用动
RejectedExecutionHandler handler //拒绝策略) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
7 大参数及其之间的关系:
int corePoolSize, //核心线程池大小
int maximumPoolSize, //最大核心线程池大小
long keepAliveTime, //超时了没有人调用就会释放
TimeUnit unit, //超时时间单位
BlockingQueue<Runnable> workQueue, //阻塞队列
hreadFactory threadFactory, //线程工厂:创建线程的,一般不用动
RejectedExecutionHandler handler //拒绝策略
2
3
4
5
6
7
举个银行窗口办理业务的例子解释 7 大参数之间的关系:
手动创建一个线程池:
public class Demo2 {
public static void main(String[] args) {
//自定义线程池,使用 ThreadPoolExecutor
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
try {
/**
* 最大乘载:阻塞队列的值 + 最大线程池大小
* 因为最大乘载是 8,这里要创建 9 个线程,所以第 9 个线程会被拒绝,根据拒绝策略会抛出异常
* java.util.concurrent.RejectedExecutionException
*/
for(int i = 1; i <= 9; i++) {
//使用线程池之后,使用线程池来创建线程
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 创建成功");
});
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
//线程池用完,程序结束,要关闭线程池
threadPool.shutdown();
}
}
}
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
- 线程池的四种拒绝策略
new ThreadPoolExecutor.AbortPolicy()
:银行满了,还有人进来,不处理这个人的,抛出异常new ThreadPoolExecutor.CallerRunsPolicy()
:哪来的去哪里new ThreadPoolExecutor.DiscardPolicy()
:队列满了,丢掉任务,不会抛出异常new ThreadPoolExecutor.DiscardOldestPolicy()
:队列满了,尝试去和最早的竞争,也不会抛出异常
- 线程池的最大线程数如何去设置?
可以根据 IO 密集型或者 CPU 密集型来考虑
CPU 密集型:看服务器的 CPU 和核数是多少,有几核最大线程数就设置为几,这样可以保持 CPU 的效率最高
IO 密集型:看你的程序中有几个十分消耗 IO 的线程,有几个最大线程数就设置为它的两倍
# 15. 四大函数式接口
- 函数式接口
函数式接口就是只有一个方法的接口
只要是函数式接口就可以用 Lambda 表达式简化
比如常见的 Runnable 接口就是函数式接口
- 四大函数式接口
- Consumer 接口
- Function 接口
- Predicate 接口
- Supplier 接口
(1)Function 函数式接口
Function 函数式接口的作用就是有一个输入参数,有一个返回值
public class FunctionDemo {
public static void main(String[] args) {
/**
* 使用 Function 函数式接口
*/
Function<String, String> function1 = new Function<String, String>() {
@Override
public String apply(String o) {
//业务代码,可以对传入的参数处理等等
System.out.println("传入的参数 = " + o);
return "返回处理后的值 = " + o;
}
};
/**
* 使用 Lambda 表达式对函数式接口进行简化
*/
Function<String, String> function2 = (o) -> {
//业务代码,可以对传入的参数处理等等
System.out.println("传入的参数 = " + o);
return "返回处理后的值 = " + o;
};
System.out.println(function1.apply("abc"));
System.out.println(function2.apply("def"));
}
}
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
(2)Predicate 函数式接口
Predicate 函数式接口的作用就是用来判断输入的参数是否符合要求,符合就返回 true,不符合就返回 false,Predicate 函数式接口返回值只能是布尔值
public class PredicateDemo {
public static void main(String[] args) {
/**
* 判断字符串是否为空
*/
Predicate<String> predicate1 = new Predicate<String>() {
@Override
public boolean test(String s) {
//自己的判断逻辑
return s.isEmpty();
}
};
/**
* 使用 Lambda 表达式简化
*/
Predicate<String> predicate2 = (s) -> {
//自己的判断逻辑
return s.isEmpty();
};
System.out.println(predicate1.test("abc"));
System.out.println(predicate1.test(""));
System.out.println(predicate2.test("def"));
System.out.println(predicate2.test(""));
}
}
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
(3)Consumer 函数式接口
Consumer 函数式接口只有传入一个参数,没有返回值
public class ConsumerDemo {
public static void main(String[] args) {
Consumer<String> consumer1 = new Consumer<String>() {
@Override
public void accept(String o) {
System.out.println("传入的参数 = " + o);
}
};
/**
* 使用 Lambda 表达式简化
*/
Consumer<String> consumer2 = (o) -> {
System.out.println("传入的参数 = " + o);
};
consumer1.accept("abc");
consumer2.accept("def");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(4)Supplier 函数式接口
Supplier 函数式接口没有传入参数,只有返回值
public class SupplierDemo {
public static void main(String[] args) {
Supplier<String> supplier1 = new Supplier<String>() {
@Override
public String get() {
return "abc";
}
};
/**
* 使用 Lambda 表达式简化
*/
Supplier<String> supplier2 = () -> {
return "def";
};
System.out.println(supplier1.get());
System.out.println(supplier2.get());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 16. Stream 流式计算
集合是用来存储数据的,而 Stream 流可以用来计算数据
public class Demo {
public static void main(String[] args) {
User u1 = new User(1, "a", 21);
User u2 = new User(2, "b", 22);
User u3 = new User(3, "c", 23);
User u4 = new User(4, "d", 24);
User u5 = new User(5, "e", 25);
//集合用来存储数据
List<User> list = Arrays.asList(u1, u2, u3, u4, u5);
//Stream 流用于计算
//需求:对这 5 个用户进行筛选
//1. ID 必须是偶数
//2. 年龄必须大于 23 岁
//3. 用户名转为大写字母
//4. 用户名字母倒着排序
//5. 只输出一个用户
list.stream()
.filter((u) -> {
return u.getId() % 2 == 0;
})
.filter((u) -> {
return u.getAge() > 23;
})
.map((u) -> {
return u.getName().toUpperCase();
})
.sorted((o1, o2) -> {
return o2.compareTo(o1);
})
.limit(1)
.forEach(System.out::println);
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class User {
private Integer id;
private String name;
private Integer age;
}
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
# 17. ForkJoin
# 18. 异步回调
# 19. JMM
- Volatile
Volatile 是 Java 虚拟机提供的轻量级的同步机制
(1)保证可见性
(2)不保证原子性
(3)禁止指令重排
- JMM
JMM 是 Java 的内存模型,是不存在的东西,是概念、约定
关于 JMM 的同步的约定:
- 线程解锁前,必须把共享变量立刻刷回主存
- 线程加锁前,必须读取主存中的最新值到工作内存中
- 加锁和解锁是同一把锁
- 线程在内存中的工作机制
线程在操作内存是不是直接操作主内存,而是操作线程自己的工作内存,然后把数据同步到主内存
内存交互操作有 8 种,虚拟机实现必须保证每一个操作都是原子性的,不可再分的(对于 double 和 long 类型的变量来说,load、store、read 和 write 操作在某些平台上允许例外)
- lock:作用于主内存的变量,把一个变量标识为线程独占状态
- unlock:作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其它线程锁定
- read:作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的 load 动作使用
- load:作用于工作内存的变量,它把 read 操作从主存中变量放入工作内存中
- use:作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用这个指令
- assign:作用于工作内存中的变量,它把一个从执行引擎中接收到的值放入工作内存的变量副本中
- store:作用于主内存中的变量,它把一个从工作内存中的一个变量的值传送到主内存中,以便后续的 write 使用
- write:作用于主内存中的变量,它把 store 操作从工作内存中得到的变量的值放入主内存的变量中
JMM 对这八种指令的使用,制定了如下规则:
- 不允许 read 和 load、store 和 write 操作单独出现,即使用了 read 必须 load,使用了 store 必须 write
- 不允许线程丢弃它最近的 assign 操作,即工作变量的数据改变了之后,必须告知主存
- 不允许一个线程将没有 assign 的数据从工作内存同步回主内存
- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量,就是对变量实施 use、store 操作之前,必须经过 assign 和 load 操作
- 一个变量同一时间只有一个线程能对其进行 lock,多次 lock 后,必须执行相同次数的 unlock 才能解锁
- 如果对一个变量进行 lock 操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新 load 或 assign 操作初始化变量的值
- 如果一个变量没有被 lock,就不能对其进行 unlock 操作,也不能 unlock 一个被其它线程锁住的变量
- 对一个变量进行 unlock 操作之前,必须把此变量同步回主内存
- Volatile 保证可见性
代码举例:
public class Invisibility {
private static int num = 0;
public static void main(String[] args) {
new Thread(() -> {
while (num == 0) {}
}).start();
try {
TimeUnit.SECONDS.sleep(1); //保证先启动子线程再启动 main 线程
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
num = 1;
System.out.println(num);
/**
* 这里会输出 1 然后卡住
* 因为先启动子线程,子线程一直在循环,然后启动 main 线程将 num 的值改为 1,但是子线程对主内存中的值是不可见的
* 所以子线程不知道 num 的值已经被修改了,所以会一直卡在那
*/
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
当给 num 加上 volatile 关键字后程序会在输出 1 后立马停止,因为 volatile 可以保证可见性,当 main 线程把 num 值修改为 1 后子线程知道 num = 1 了,所以循环就停了
- Volatile 不保证原子性
public class AtomicityIsNotGuaranteed {
private static int num = 0;
public static void add() {
num++;
}
public static void main(String[] args) {
//理论上 num 的值应该是 20000
for(int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int j = 1; j <= 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + " " + num);
/**
* 这里理论上是 20000,其实每次都不一定是两万
*
* 如果给 add 方法加上 synchronized 锁的话就可以保证结果是 20000
* 因为 synchronized 锁可以保证原子性,一次只能有一个线程获取到锁
*
* 如果给 num 变量加上 volatile 关键字的话也不一定是 20000
* 因为 Volatile 不保证原子性
*/
}
}
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
那么如果不加 lock 和 synchronized 怎么样保证原子性?
public static void add() {
num++;
}
2
3
这里的 num++ 操作看着是一步操作,实则不是一步操作,num++ 在转换为字节码时其实是几步操作,所以 num++ 本身就不是一个原子性操作
我们可以使用原子类解决原子性问题:
public class AtomicityIsNotGuaranteed {
/**
* 使用原子类保证原子性
*/
private static AtomicInteger num = new AtomicInteger();
public static void add() {
num.getAndIncrement(); //执行 +1 操作
}
public static void main(String[] args) {
//理论上 num 的值应该是 20000
for(int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int j = 1; j <= 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + " " + num);
}
}
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
这些原子类的底层都直接和操作系统挂钩,在内存中修改值。想要了解原子类,就要了解 unsafe 类和 CAS
- 指令重排
指令重排:我们自己写的程序,计算机并不是按照我们写的程序的顺序去执行的
处理器会对没有数据之间的依赖性的指令进行重排
int x = 1; //1
int y = 2; //2
x = x + 5; //3
y = x * x; //4
//我们所期望的执行顺序:1234
//但是可能执行的时候会重排:2134,1324,因为它们之间没有依赖性,所以可能重排
2
3
4
5
6
7
如果有两个线程:
线程 A | 线程 B |
---|---|
x = a | y = b |
b = 1 | a = 2 |
正常的结果:x = 0,y = 0,但是有可能会指令重排
线程 A | 线程 B |
---|---|
b = 1 | a = 2 |
x = a | y = b |
指令重排导致的结果:x = 2,y = 1
使用 Volatile 可以避免指令重排,Volatile 会加一个内存屏障保证特定的操作的执行顺序
# 20. 单例模式
饿汉式单例模式
/**
* 饿汉式单例模式
*/
public class HungryManStyle {
/**
* 饿汉式单例模式的缺点:会造成空间浪费
* 比如下面有一些初始化代码,我没用到它也创建出来了
*/
private byte[] data1 = new byte[1024 * 1024];
private byte[] data2 = new byte[1024 * 1024];
private byte[] data3 = new byte[1024 * 1024];
private byte[] data4 = new byte[1024 * 1024];
/**
* 构造器私有化
*/
private HungryManStyle() {
}
private final static HungryManStyle HUNGRY_MAN_STYLE = new HungryManStyle();
public static HungryManStyle getInstance() {
return HUNGRY_MAN_STYLE;
}
}
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
懒汉式单例模式
(1)单线程下的懒汉式单例模式
/**
* 懒汉式单例模式
*/
public class LazyStyle {
/**
* 构造器私有化
*/
private LazyStyle() {
System.out.println(Thread.currentThread().getName() + " 创建成功");
}
private static LazyStyle lazyStyle;
/**
* 这在单线程模式下是可行的,但是在多线程下会失效
* @return
*/
public static LazyStyle getInstance() {
if (lazyStyle == null) {
lazyStyle = new LazyStyle();
}
return lazyStyle;
}
/**
* 测试
*/
public static void main(String[] args) {
/**
* 多线程下的懒汉式单例模式
*/
for (int i = 0; i < 10; i++) {
new Thread(() -> {
LazyStyle.getInstance();
}).start();
}
}
}
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
(2)多线程下的懒汉式单例模式
/**
* 懒汉式单例模式
*/
public class LazyStyle {
/**
* 构造器私有化
*/
private LazyStyle() {
System.out.println(Thread.currentThread().getName() + " 创建成功");
}
private static LazyStyle lazyStyle;
/**
* 多线程下的懒汉式单例模式 -> DCL 懒汉式
* @return
*/
public static LazyStyle getInstance() {
if (lazyStyle == null) {
synchronized (LazyStyle.class) {
if (lazyStyle == null) {
lazyStyle = new LazyStyle();
}
}
}
return lazyStyle;
}
/**
* 测试
*/
public static void main(String[] args) {
/**
* 多线程下的懒汉式单例模式
*/
for (int i = 0; i < 10; i++) {
new Thread(() -> {
LazyStyle.getInstance();
}).start();
}
}
}
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
但是有一个问题,因为在执行 lazyStyle = new LazyStyle();
这段代码它不是原子性的
/**
* 懒汉式单例模式
*/
public class LazyStyle {
/**
* 构造器私有化
*/
private LazyStyle() {
System.out.println(Thread.currentThread().getName() + " 创建成功");
}
private volatile static LazyStyle lazyStyle;
/**
* 多线程下的懒汉式单例模式 -> DCL 懒汉式
* @return
*/
public static LazyStyle getInstance() {
if (lazyStyle == null) {
synchronized (LazyStyle.class) {
if (lazyStyle == null) {
lazyStyle = new LazyStyle(); //不是一个原子性操作
/**
* 字节码有三步操作
* 1. 分配内存空间
* 2. 执行构造方法,初始化对象
* 3. 把这个对象指向分配的内存空间
* 我们期望的执行顺序:123
* 但是因为指令重排可能会:132
* 这样就会让对象指向一个空的内存空间,此时又有一个线程进来,就会判断 lazyStyle 对象为空
* 解决方法就是加上 volatile 关键字
*/
}
}
}
return lazyStyle;
}
/**
* 测试
*/
public static void main(String[] args) {
/**
* 多线程下的懒汉式单例模式
*/
for (int i = 0; i < 10; i++) {
new Thread(() -> {
LazyStyle.getInstance();
}).start();
}
}
}
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
但是这是不安全的,可以利用反射进行破解:
/**
* 懒汉式单例模式
*/
public class LazyStyle {
/**
* 构造器私有化
*/
private LazyStyle() {
}
private volatile static LazyStyle lazyStyle;
/**
* 多线程下的懒汉式单例模式 -> DCL 懒汉式
* @return
*/
public static LazyStyle getInstance() {
if (lazyStyle == null) {
synchronized (LazyStyle.class) {
if (lazyStyle == null) {
lazyStyle = new LazyStyle();
}
}
}
return lazyStyle;
}
/**
* 反射
*/
public static void main(String[] args) throws Exception {
LazyStyle instance = LazyStyle.getInstance();
Constructor<LazyStyle> declaredConstructor = LazyStyle.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
LazyStyle instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}
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
解决方法就是使用枚举,枚举默认是单例的并且不能被反射破坏:
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance() {
return INSTANCE;
}
}
class Test {
public static void main(String[] args) throws Exception {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
/**
* 报错:java.lang.IllegalArgumentException: Cannot reflectively create enum objects
*/
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 21. CAS
什么是 CAS:
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2025);
/**
* 2025:期望的值
* 2026:更新的值
* 如果是期望的值,就更新,否则就不更新,CAS 是 CPU 的并发原语
*/
System.out.println(atomicInteger.compareAndSet(2025, 2026));
System.out.println(atomicInteger.get());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
CAS 底层调用的是 unsafe 类的 compareAndSwapInt 方法,那么什么是 unsafe 类?
Java 无法操作内存,但是 Java 可以调用 C++,就是 native 的方法,然后 C++ 去操作内存。unsafe 类是 Java 的后门,可以通过这个类操作内存
CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么执行操作,如果不是就一直循环
CAS 缺点:
- 循环会耗时
- 一次性只能保证一个共享变量的原子性
- ABA 问题
什么是 ABA 问题:
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2025);
/**
* 捣乱的线程
*/
System.out.println(atomicInteger.compareAndSet(2025, 2026));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2026, 2025));
System.out.println(atomicInteger.get());
/**
* 期望的线程
*/
System.out.println(atomicInteger.compareAndSet(2025, 6666));
System.out.println(atomicInteger.get());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
想要解决 ABA 问题,可以使用原子引用,其思想就是乐观锁
public class CASDemo {
public static void main(String[] args) {
//AtomicInteger atomicInteger = new AtomicInteger(2025);
/**
* 使用原子引用
* 第一个 1:期望的值
* 第二个 1:版本号
*/
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);
new Thread(() -> {
int stamp = atomicStampedReference.getStamp(); //获得版本号
System.out.println("线程 A 第一次获取版本号 = " + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
/**
* 1:表示期望的值
* 2:表示要修改成的值
* atomicStampedReference.getStamp():期望的版本号
* atomicStampedReference.getStamp() + 1:新的版本号
*/
System.out.println(atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("线程 A 第二次获取版本号 = " + atomicStampedReference.getStamp());
System.out.println(atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("线程 A 第三次获取版本号 = " + atomicStampedReference.getStamp());
}, "a").start();
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println("线程 B 第一次获取版本号 = " + stamp);
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(atomicStampedReference.compareAndSet(1, 6, stamp, stamp + 1));
System.out.println("线程 B 第二次获取版本号 = " + atomicStampedReference.getStamp());
}, "b").start();
}
}
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
# 22. 锁
- 公平锁和非公平锁
公平锁:非常公平,不能够插队,必须先来后到
非公平锁:非常不公平,可以插队
- 可重入锁
可重入锁又叫递归锁
- 自旋锁
- 死锁
怎么找到死锁:
- 使用
jps -l
定位进程号 - 使用
jstack 进程号
找到死锁问题