这部分主要介绍Java相关的多线程知识以及多线程相关的常用设计模式,第一部分主要是Java多线程的基础,线程以及并发相关的概念就不在这里赘述了,主要针对Java相关的内容。

线程的启动

利用Thread类的子类

1
2
3
4
5
6
7
8
9
10
11
public class NewThread extends Thread {
public void run() {
...
}
}

public class Main {
public static void main(String[] args) {
new NewThread().start();
}
}

首先需要多线程执行的类需要继承Thread类,并重写run方法,新的线程启动后就会执行run方法。具体来说,启动一个新线程需要创建一个继承于Thread类的对象,并调用start方法。我们需要区别,NewThread类并不是线程,创建它并不会启动线程,线程结束NewThread类的实例也不会消失,只有调用start方法才会启动一个线程。

利用Runnable接口

1
2
3
4
5
6
7
8
9
public class NewThread implements Runnable {
public void run() {
...
}
}

public class Main {
new Thread(new NewThread()).start();
}

我们可以看到,利用Runnable接口与利用Thread子类很像,都需要在具体类中重写run方法,区别是使用Thread类的子类时我们需要实例化一个子类的实例并调用它的start方法即可,而使用Runnable接口时我们需要把Runnable的对象作为参数去实例化一个Thread对象,并调用Thread类的start方法。

利用concurrent包中的工厂创建

上面利用Runnable接口的方式似乎不够优雅,因为创建时需要连续使用两个new,我们可以利用Java提供的工厂模式来隐藏这一部分。

1
2
3
4
5
6
7
8
9
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

public class Main {
public static void main(String[] args) {
ThreadFactory factory = Executors.defaultThreadFactory();
factory.newThread(new NewThread()).start();
}
}

获取默认工厂后,把Runnable对象作为参数传给工厂的newThread方法,就可以完成对象的创建过程,然后调用start方法即可启动线程。

线程的暂停

1
2
3
4
5
try {
Thread.sleep(1000);
} catch (InterruptedException e) {

}

上面的sleep方法可以使当前线程暂停1000ms,注意使用sleep方法时,需要捕捉InterruptedException异常,关于该异常的具体细节会在后续给出。

线程的互斥

Java使用synchronized关键字来申明只能同时被一个线程执行的方法或代码段。

1
2
3
public synchronized void test() {
...
}

上述的代码表示对test方法加锁,如果一个线程在执行test方法(一个线程获得锁),其他的线程必须等待当前线程释放锁才能执行。

synchronized关键字也可以用于代码块,如上述代码就等价于:

1
2
3
4
5
public void test() {
synchronized (this) {
...
}
}

synchronized关键字后的括号表示获取锁的实例。

线程的协作

每一个线程都有一个等待队列,队列中有等待执行的线程。

已经获取锁的线程可以执行wait()方法,这会使得当前线程暂停执行,进入等待序列并释放锁。

已经获取锁的线程可以执行notify或notifyAll方法,前者随机唤醒等待队列中的一个线程,后者唤醒等待队列中的所有线程,但只有获得锁的线程可以执行。notify方法与notifyAll方法相比效率更高,但notifyAll方法的健壮性更好。

线程的状态变化图