跳到主要内容

02、Java并发编程 - 进程和线程回顾

2、进程和线程回顾

2.1. 什么是进程 /线程

进程 / 线程是什么?

进程:像 QQ.exe 、 Music.exe 、程序就是一个进程

线程:一个进程中可能包含多个线程,至少包含一个。JAVA至少包括两个线程:main和GC 线程。

2.2. 并发/并行是什么

并发 / 并行是什么?

并发编程? 并发。并行;

并发:多线程,多个线程操作一个资源类,CPU快速交替的执行。

并行:多核多CPU。多核多CPU可以并行执行。

举例:

你吃饭,吃到一半,电话来了,3种情况

1、 吃完再去接电话(单线程);

2、 先接电话再吃(交替,也就是并发);

3、 边吃边接电话(并行);

一个CPU 的电脑,能不能并行执行任务?

不能。必须在多核多CPU下才能并行执行。

CPU密集型、IO密集型,这两个概念后面再说。

所以说,并发编程的主要目的是:充分利用CPU的资源,提高性能!

2.3. 线程的状态

线程的状态

线程的状态有6种,是定义在Thread类中的枚举类State。

 

源码如下:

public enum State {
   
     
 
    NEW,

    RUNNABLE,

    BLOCKED,

    WAITING, // 等待

    TIMED_WAITING, // 延时等待 

    TERMINATED; //
}

线程状态。 线程可以处于以下状态:

NEW
尚未启动的线程,处于此状态。
RUNNABLE
在Java虚拟机中执行的线程,处于此状态。
BLOCKED
被阻塞等待监视器锁定的线程,处于此状态。
WAITING
正在等待 执行特定动作的线程,处于此状态。
TIMED_WAITING
正在等待另一个线程执行动作达到指定等待时间的线程,处于此状态。
TERMINATED(终止、结束)
已退出的线程,处于此状态。

2.4. 线程阻塞

2.4.1. 导致线程阻塞的方法

阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪),Java 提供了大量方法来支持阻塞,如下方法:

方法 说明
sleep() sleep() 允许 指定以毫秒为单位的一段时间作为参数,它使得线程在指定的时间内进入阻塞状态,不能得到CPU 时间,指定的时间一过,线程重新进入可执行状态。
应用场景:sleep() 被用在等待某个资源就绪的情形:测试发现条件不满足后,让线程阻塞一段时间后重新测试,直到条件满足为止。
suspend() 和 resume() 两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume() 被调用,才能使得线程重新进入可执行状态。
应用场景:suspend() 和 resume() 被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后,调用 resume() 使其恢复。
yield() yield() 使得线程放弃当前分得的 CPU 时间,但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。
调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间,从而转到另一个线程。
wait() 和 notify() 两个方法配套使用,wait() 使得线程进入阻塞状态,它有两种形式,一种允许 指定以毫秒为单位的一段时间作为参数,另一种没有参数,前者当对应的 notify() 被调用或者超出指定时间时线程重新进入可执行状态,后者则必须对应的 notify() 被调用。

2.4.2. wait(),notify()和suspend(),resume()之间的区别

1、 sleep()、suspend()和resume()、yield()阻塞时都不会释放占用的锁(如果占用了的话),wait(),notify()方法阻塞时要释放占用的锁这是最核心的区别,这一核心区别导致了一系列细节上的区别(如下几条区别);

2、 wait(),notify()方法属于Object前面叙述的所有方法都隶属于Thread类,但是这一对却直接隶属于Object类也就是说,所有对象都拥有这一对方法初看起来这十分不可思议,但是实际上却是很自然的,因为这一对方法阻塞时要释放占用的锁,而锁是任何对象都具有的,调用任意对象的wait()方法导致线程阻塞,并且该对象上的锁被释放而调用任意对象的notify()方法则导致因调用该对象的wait()方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行);

3、 wait(),notify()方法必须在synchronized方法或块中调用前面叙述的所有方法都可在任何位置调用,但是这一对方法却必须在synchronized方法或块中调用,理由也很简单,只有在synchronized方法或块中,当前线程才占有锁,才有锁可以释放同样的道理,调用这一对方法的对象上的锁必须为当前线程所拥有,这样才有锁可以释放因此,这一对方法调用必须放置在这样的synchronized方法或块中,该方法或块的上锁对象就是调用这一对方法的对象若不满足这一条件,则程序虽然仍能编译,但在运行时会出现IllegalMonitorStateException异常;

为什么wait()方法和notify()/notifyAll()方法要在同步块中被调用

这是JDK强制的,wait()方法和notify()/notifyAll()方法在调用前都必须先获得对象的锁。

2.4.3. wait() 和 notify()与操作系统进程间的通信机制结合

wait() 和 notify() 方法的上述特性决定了它们经常和synchronized 方法或块一起使用。

将它们和操作系统的进程间通信机制作一个比较就会发现它们的相似性:

synchronized方法或块提供了类似于操作系统原语的功能,它们的执行不会受到多线程机制的干扰,而这一对方法则相当于 block 和wakeup 原语(这一对方法均声明为 synchronized)。它们的结合使得我们可以实现操作系统上一系列精妙的进程间通信的算法(如信号量算法),并用于解决各种复杂的线程间通信问题。

关于wait() 和 notify() 方法最后再说明两点:

第一:调用 notify() 方法导致解除阻塞的线程是从因调用该对象的 wait() 方法而阻塞的线程中随机选取的,我们无法预料哪一个线程将会被选择,所以编程时要特别小心,避免因这种不确定性而产生问题——虚假唤醒
第二:除了 notify(),还有一个方法 notifyAll() 也可起到类似作用,唯一的区别在于,调用 notifyAll() 方法将把因调用该对象的 wait() 方法而阻塞的所有线程一次性全部解除阻塞。当然,只有获得锁的那一个线程才能进入可执行状态。
谈到阻塞,就不能不谈一谈死锁,略一分析就能发现,suspend() 方法和不指定超时期限的 wait() 方法的调用都可能产生死锁。遗憾的是,Java 并不在语言级别上支持死锁的避免,我们在编程中必须小心地避免死锁。

2.4.4. wait()方法和notify()/notifyAll()方法在放弃对象监视器时有什么区别

wait()方法和notify()/notifyAll()方法在放弃对象监视器的时候的区别在于:

wait()方法立即释放对象监视器;

notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃对象监视器。

2.4.5. wait / sleep 的区别

wait / sleep 的区别

主要区别有以下四种:

1、类

wait :属于Object类

sleep :属于Thread类 , 谁调用的谁睡觉!如:线程A 调用了 线程B 的sleep方法,实际上是谁睡觉?线程A在睡觉。

2、是否释放锁

sleep抱着锁睡觉。

wait 会释放锁!

3、使用范围不同

wait、notify、notifyAll 只能用在同步方法中或者同步代码块中;

Sleep 可以再任意地方使用;

4、异常

sleep, 必须捕获异常!

wait , 不需要捕获异常!

**

sleep抱着锁睡觉。

wait 会释放锁!

3、使用范围不同

wait、notify、notifyAll 只能用在同步方法中或者同步代码块中;

Sleep 可以再任意地方使用;

4、异常

sleep, 必须捕获异常!

wait , 不需要捕获异常!