顺序、并发与并行
顺序
用于表示多个操作“依次处理”。比如把十个操作交给一个人来处理时,这个人要一个一个地按顺序来处理
并行
用于标识多个操作“同时处理”。比如十个操作分给两个人处理时,这两个人就会并行来处理。
并发
相对于顺序和并行来说比较抽象,用于表示“将一个操作分割成多个部分并且允许无序处理”。比如将十个操作分成相对独立的两类,这样便能够开始并发处理了。如果一个人来处理,这个人就是顺序处理分开的并发操作,而如果是两个人,这两个人就可以并行处理同一个操作。
总结
多线程程序都是并发处理的。如果 CPU 只有一个,那么并发处理就是顺序执行的,而如果有多个 CPU,那么并发处理就可能会并行运行。
并发处理的顺序执行与并发处理的并行执行示意图如下所示
线程启动与中止
启动方式
- 利用
Thread
类的子类的实例启动线程 - 利用
Runnable
接口的实现类的实例启动线程
以上两种方式都需要使用 start 方法用于启动新的线程,在此需要注意的事情是,启动新线程调用的是 start 方法而不是 run 方法
终止
直到所有的线程都终止后,程序才会终止。也就是说,当这两个线程都终止后,程序才会终止。
Java 程序的终止是指除守护线程以外的线程全部终止。守护线程是执行后台作业的线程。我们可以通过
setDaemon
方法把线程设置为守护线程。
小知识
java.util.concurrent 包中包含一个将线程创建抽象化的 ThreadFactory 接口。利用该接口,我们可以将 Runnable 作为传入参数并通过 new 创建 Thread 实例的处理隐藏在 ThreadFactory 内部。
Executors 类中含有多种创建 ThreadFactory 的方法,感兴趣的可以去看一下源码
synchronized 相关
synchronized 方法
如果声明一个方法时,在前面加上关键字 synchronized 那么这个方法就只能由一个线程运行。只能由一个线程运行是每次只能由一个线程运行的意思,并不是说仅能让某一特定线程运行。这种方法叫做 synchronized,有时也称为同步方法。
synchronized void method() { ... }复制代码
synchronized 代码块
如果只是想让方法中的某一部分由一个线程运行,而非整个方法,则可使用 synchronized 代码块
synchronized (表达式) { ... }复制代码
synchronized 实例方法和 synchronized 代码块
假设有如下 synchronized 实例方法
synchronized void method() { ... }复制代码
这跟下面将方法体用 synchronized 代码块包围起来是等效的
void method() { synchronized (this) { ... }}复制代码
synchronized 实例方法是使用 this 的锁来执行线程的互斥处理的
synchronized 静态方法和 synchronized 代码块
synchronized 静态方法和 synchronized 实例方法是相同的。但是 synchronized 静态方法使用的锁和 synchronized 实例方法使用的锁是不一样的
class Something { static synchronized void method() { ... }}复制代码
这跟下面将方法体用 synchronized 代码块包围起来是等效的
class Something { static void method() { synchronized (Something.class) { ... } }}复制代码
synchronized 静态方法是使用该类的类对象锁来执行线程的互斥处理的。 Something.class 是 Something 类对应的 java.lang.class 类的实例
wait、notify 和 notifyAll
等待队列
所有实例都拥有一个等待队列,它是在实例的 wait 方法执行后停止操作的线程队列。就好比为每个实例准备的线程休息室
在执行 wait 方法后,线程便会暂停操作,进入等待队列这个休息室。除非发生下列某一情况,否则线程会一直在等待队列中休眠。
- 有其他线程的 notify 方法来唤醒线程
- 有其他线程的 notifyAll 方法来唤醒线程
- 有其他线程的 interrupt 方法来唤醒线程
- wait 方法超时
若要执行 wait 方法,线程必须持有锁。但如果线程进入等待队列,便会释放其实例的锁
notify 方法
该方法会将等待队列中的一个线程去除。同 wait 方法一样,若要执行 notify 方法,线程也必须持有要调用的实例的锁。
notify 唤醒的线程并不会在执行 notify 的一瞬间就重新运行。因为在执行 notify 的那一瞬间,执行 notify 的线程还持有着锁,所以其他线程还无法获取这个实例的锁
notifyAll 方法
notify 方法仅唤醒一个线程,而 notifyAll 则唤醒所有线程,这是两者之间唯一的区别
同 wait 方法和 notify 方法一样, notifyAll 方法也只能由持有要调用的实例锁的线程调用
notify 和 notifyAll 选择
notify 方法和 notifyAll 方法非常相似,到底该使用哪个?
实际上,这很难选择,由于 notify 唤醒的线程较少,所以处理速度要比使用 notifyAll 时快。 但使用 notify 时,如果处理不好,程序便可能会停止。一般来说,使用 notifyAll 时的代码要比使用 notify 时的更为健壮。