JUC介绍

在 java 5.0 提供了 java.util.concurrent (简称JUC )包,在此包中增加了在并发编程中很常用的实用工具类,用于定义类似于线程的自定义子系统,包括线程池、异步 IO 和轻量级任务框架。提供可调的、灵活的线程池。还提供了设计用于多线程上下文中的 Collection 实现等。

image-20200923111829563

程序进程线程

程序进程线程

在操作系统中运行的程序就是进程。一个进程可以有多个线程,如视频中同时听声音,看图像,看弹幕等等。

线程和进程

程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。

进程则是执行程序的一次执行过程,是一个动态的概念。是系统资源分配的单位。通常一个进程中可以包含若干个线程,一个进程中至少有一个线程(main线程)。线程是CPU调度和执行的单位。

线程状态

在java中查看Thread.State可知线程的状态

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
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,

/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,

/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,

/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,

/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,

/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}

NEW(新建)

image-20200923113444357

当线程被创建出来还没有被调用start()时候的状态

RUNNABLE(就绪)

image-20200923113611223

当线程被调用了start(),且处于等待操作系统分配资源(如CPU)、等待IO连接、正在运行状态,即表示Running状态和Ready状态。

注:不一定被调用了start()立刻会改变状态,还有一些准备工作,这个时候的状态是不确定的。

BLOCKED(阻塞)

image-20200923113721654

等待监视锁,这个时候线程被操作系统挂起。当进入synchronized块/方法或者在调用wait()被唤醒/超时之后重新进入synchronized块/方法,锁被其它线程占有,这个时候被操作系统挂起,状态为阻塞状态。

阻塞状态的线程,即使调用interrupt()方法也不会改变其状态。

WAITING(等待)

image-20200923114636415

无条件等待,当线程调用wait()/join()/LockSupport.park()不加超时时间的方法之后所处的状态,如果没有被唤醒或等待的线程没有结束,那么将一直等待,当前状态的线程不会被分配CPU资源和持有锁。

TIMED_WAITING(限时等待)

image-20200923114953181

有条件的等待,当线程调用sleep(睡眠时间)/wait(等待时间)/join(等待时间)/ LockSupport.parkNanos(等待时间)/LockSupport.parkUntil(等待时间)方法之后所处的状态,在指定的时间没有被唤醒或者等待线程没有结束,会被系统自动唤醒,正常退出

TERMINATED(终止)

image-20200923115105668

执行完了run()方法

其实这只是Java语言级别的一种状态,在操作系统内部可能已经注销了相应的线程,或者将它复用给其他需要使用线程的请求,而在Java语言级别只是通过Java代码看到的线程状态而已。

img

wait / sleep 的区别

来自不同的类

这两个方法来自不同的类分别是,sleep来自Thread类,和wait来自Object类。

sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用了b的sleep方法,实际上还是a 去睡觉,要让b线程睡觉要在b的代码中调用sleep。

有没有释放锁(释放资源)

最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。

wait放开手去睡,放开手里的锁sleep握紧手去睡,醒了手里还有锁

sleep是线程被调用时,占着cpu去睡觉,其他线程不能占用cpu,os认为该线程正在工作,不会让出系 统资源,wait是进入等待池等待,让出系统资源,其他线程可以占用cpu。

sleep(100L)是占用cpu,线程休眠100毫秒,其他进程不能再占用cpu资源,wait(100L)是进入等待池 中等待,交出cpu等系统资源供其他进程使用,在这100毫秒中,该线程可以被其他线程notify,但不同 的是其他在等待池中的线程不被notify不会出来,但这个线程在等待100毫秒后会自动进入就绪队列等待 系统分配资源,换句话说,sleep(100)在100毫秒后肯定会运行,但wait在100毫秒后还有等待os调 用分配资源,所以wait100的停止运行时间是不确定的,但至少是100毫秒。

就是说sleep有时间限制的就像闹钟一样到时候就叫了,而wait是无限期的除非用户主动notify。

使用范围不同

wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用

是否需要捕获异常?

sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常。

看了下源码发现sleep和wait都抛出了异常需要捕获

image-20200923150835150

image-20200923150928545

因此存在怀疑。

并发和并行

并发:多线程操作同一个资源。在CPU一核的情况下,快速交替可以达到模拟多条线程同时执行的效果。

并行:CPU多核,多个线程可以同时执行

并发:同一时刻多个线程在访问同一个资源,多个线程对一个点 例子:小米9今天上午10点,限量抢购,春运抢票 电商秒杀…

并行:多项工作一起执行,之后再汇总 例子:泡方便面,电水壶烧水,一边撕调料倒入桶中

并发编程的本质:充分利用CPU的资源

虚假唤醒问题

案例

首先我们先写一个生产者消费者案例。当生产者生产数量为1的时候,通知消费者消费。当生产者产品数量为0的时候,生产产品。

image-20200924100814619

image-20200924100947743

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
/**
* 线程之间的通信问题:生产者和消费者问题!等待唤醒,通知唤醒
* 线程交替执行 A B 操作同一个变量 num = 0
* A num+1
* B num-1
*/
public class A {

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);
//通知其他线程,我+1完成了
this.notifyAll();
}

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

}

代码编写完成运行进行测试。

image-20200924101154892

与我们设想的结果是一致的!但是这个代码还是存在问题的,也就是虚假唤醒问题。目前我们的生产者消费者只有一对,当我们在加入一对生产者和消费者后。问题就暴露出来了。

image-20200924101556523

此时再次运行,查看结果

image-20200924101747569

可以发现出现了许多与我们设想不符的结果,例如生产者生产了两个产品。这就是虚假唤醒问题的体现!为什么会出现这种结果呢。

解释

首先我们要查看一下wait方法的文档。

image-20200924102015386

对这种现象的解释是:目前我们使用的是If来进行判断是否线程wait。而当多对生产者消费者同时运行时,假设第一次是生产者A线程被调用执行,此时的number为0,numner++,number变为了1。

image-20200924103059022

接着CPU再次接着调度线程,此时生产者C被调用执行。经过if判断,当前的number不为0,此线程被wait方法阻塞,释放锁,等待唤醒。

image-20200924104632987

然后CPU又再次接着调度线程,生产者A被调用执行。经过if判断,此时的number不为0,此线程同样被wait方法阻塞,释放锁,等待唤醒。

image-20200924104834925

此时的已经存在两个生产者线程处于阻塞状态,等待唤醒!接着CPU调度线程,只能是调用消费者线程了。假设调用的是消费者线程B,此时number为1,经过if判断不等于0,消费者消费产品,唤醒当前被阻塞的生产者线程们。

image-20200924105339444

此时处于被阻塞的线程分别是,生产者A和生产者C。*因为if判断只需要进行一次判断即可。所以当线程A和C被唤醒后,分别进行后续操作。number++,打印语句。两次number++使得number的值变成了2!

image-20200924105908967

优化

由上面的过程分析可以知道,当多个线程被唤醒后,由于if条件只判断一次,所以后面再次唤醒后不能对当前条件再进行判断而导致出现虚假唤醒的问题。所以我们可以按照官方文档的建议使用while循环来判断是否进行wait

image-20200924102015386

image-20200924110303656

image-20200924110946473

再次运行查看效果,成功解决!

image-20200924110342093

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
/**
* 线程之间的通信问题:生产者和消费者问题!等待唤醒,通知唤醒
* 线程交替执行 A B 操作同一个变量 num = 0
* A num+1
* B num-1
*/
public class A {

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();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "D").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);
//通知其他线程,我+1完成了
this.notifyAll();
}

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

}