Day8 Java多线程进阶 1
uwupu 啦啦啦啦啦

JUC

java.util.concurrent

子类介绍

java.util.concurrent.atomic 原子性

java.util.concurrent.locks lock锁

在业务中

普通的线程代码:Thread

Runnable: 没有返回值,效率相对于Callable低。

线程和进程

对于Java而言的线程:Thread、Runnable、Callable。

并发和并行

并发(多线程操作同一个资源)

  • CPU一核,模拟出多条线程。天下武功,唯快不破,快速交替。

并行(多个人一起行走)

  • CPU多核,多个线程可以同时执行。线程池

线程有几个状态

六个。

1
2
3
4
5
6
NEW,//线程新生
RUNNABLE,//运行状态
BLOCKED,//阻塞
WAITING,//等待
TIMED_WAITING,//超时等待
TERMINATED;//终止

wait/sleep区别

  1. 来自不同的类

    wait -> Object

    sleep -> Thread

  2. 关于锁的释放

    wait 会释放锁

    sleep 不会释放锁

  3. 使用的范围不同

    wait 必须在同步代码块中

    sleep 可以在任何地方使用

  4. 是否需要捕获异常

    wait 不需要捕获异常

    sleep 必须要捕获异常

Lock锁

synchronized

Lock接口以及其实现类

image

Lock是一个接口,有以下实现类。

  • ReentrantLock 可重用锁

  • ReadLock 读锁

  • WriteLock 写锁

公平锁 非公平锁

image

公平锁:先来后到。

非公平锁:可以插队。(默认)

synchronized 和 Lock 区别

  1. synchronized 内置的Java关键字。 Lock是一个Java类。

  2. synchronized 无法判断获取锁的状态。Lock可以判断是否获取到了锁。

  3. synchronized 会自动释放锁。Lock必须要手动释放锁,若不释放,则会死锁

  4. synchronized 线程1(获得锁,阻塞),线程2(等待)(悲)。Lock锁不一定会一直等待,可以尝试获取锁。

  5. synchronized 可重入锁,不可中断的,非公平;Lock,可重入锁,可以判断锁,非公平(可以自己设置)。

  6. synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码。

生产者和消费者问题

synchronized版本

代码:(错误的版本,正确的版本下面)

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
/**
* 线程之间的通信问题:生产者和消费者问题。 等待唤醒,通知唤醒
* 线程交替执行
* A B 操作同一个变量 num = 0
*
*/
public class Demo3_生产者和消费者 {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
// 判断等待 业务 通知
class Data{//资源类
private int number = 0;

//+1
public synchronized void increment() throws InterruptedException {
if (number!=0){
//等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+" -> "+number);
//通知其他线程
this.notifyAll();
}
// - 1
public synchronized void decrement() throws InterruptedException {
if(number==0){
//等待
this.wait();
}
number--;
//通知其他线程
System.out.println(Thread.currentThread().getName()+" -> "+number);
this.notifyAll();
}

}

出现问题:两个线程正常运行 四个线程出现问题。 虚假唤醒。

当出现超过两个线程,就会出现虚假唤醒

当一个生产者生产结束后,会唤醒其他所有消费者,导致消费者全部开始消费,若消费者多于1个,会出现多次消费,导致错误。

为解决这个问题,可以将if换成while,来防止虚假唤醒。

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
/**
* 线程之间的通信问题:生产者和消费者问题。 等待唤醒,通知唤醒
* 线程交替执行
* A B 操作同一个变量 num = 0
*
*/
public class Demo3_生产者和消费者 {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
// 判断等待 业务 通知
class Data{//资源类
private int number = 0;

//+1
public synchronized void increment() throws InterruptedException {
while (number!=0){
//等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+" -> "+number);
//通知其他线程
this.notifyAll();
}
// - 1
public synchronized void decrement() throws InterruptedException {
while(number==0){
//等待
this.wait();
}
number--;
//通知其他线程
System.out.println(Thread.currentThread().getName()+" -> "+number);
this.notifyAll();
}

}

JUC版生产者和消费者问题

Lock用来替代synchronized的名词

image

代码实现

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
public class Demo3_生产者和消费者JUC版 {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.increment();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.increment();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.decrement();
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.decrement();
}
},"D").start();

}
}
// 判断等待 业务 通知
class Data2{//资源类
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
// condition.await();//等待
// condition.signalAll();//唤醒全部
//+1
public void increment() {
lock.lock();
try {
//业务代码
while (number!=0){
condition.await();
}
number++;
condition.signalAll();
System.out.println(Thread.currentThread().getName()+" => "+number);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
// - 1
public void decrement() {
lock.lock();
try {
while (number==0){
//等待
condition.await();
}
number--;
//通知
condition.signalAll();
System.out.println(Thread.currentThread().getName()+" => "+number);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

Condition精准通知唤醒

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
76
77
78
79
80
81
82
83
84
85
/**
* 当前需求:A执行完通知B,B执行完通知C,C执行完通知A。
*/
public class Demo4_通过生产者消费者问题学习Condition精准通知 {
public static void main(String[] args) {
Data3 data = new Data3();

new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printC();
}
},"C").start();

}
}


class Data3{//资源类
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1; // 1 A 2 B 3 C
public void printA(){
lock.lock();
try {
//业务 判断 > 执行 > 通知
while (number!=1){
//等待
condition1.await();
}
System.out.println(Thread.currentThread().getName()+" A");
//唤醒B
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
//业务 判断 > 执行 > 通知
while (number!=2){
condition2.await();
}

System.out.println(Thread.currentThread().getName()+" B");
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
//业务 判断 > 执行 > 通知
while (number != 3) {
condition3.await();
}
System.out.println(Thread.currentThread().getName()+" C");
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}

其他

  • native 本地方法。

  • 获取CPU核数方法

1
2
3
//获取CPU核数
// CPU 密集型,IO密集型
System.out.println(Runtime.getRuntime().availableProcessors());
  • 并发编程的本质:充分利用CPU的资源

一些问题

Q:Java默认有几个线程?

A:2个。main和GC。

Q: Java可以开启线程吗?

A:不可以。通过分析new Thread().start(),可以得到一个native方法private native void start0();,这个方法是个本地方法,调用的是底层的C++。Java不能直接操作硬件。

 评论