线程创建与运行
Java 中有三种线程创建方式:
- 实现 Runnable 接口的 run 方法
- 继承 Thread 类并重写 run 的方法
- 使用 FutureTask 方式。
首先看继承 Thread 类方式的实现。
public class ThreadTest {//继承 Thread 类并重写 run 方法public static class MyThread extends Thread {@Overridepublic void run() {System.out.println(「I am a child thread」);}}public static void main(String[] args) {// 创建线程MyThread thread = new MyThread();// 启动线程thread.start();}}
使用继承方式的好处是,在 run()方法内获取当前线程直接使用 this 就可以了,无须使用 Thread.currentThread()方法;而如果使用 Runnable 方式,则只能使用主线程里面被声明为 final 的变量。
不好的地方是 Java 不支持多继承,如果继承了 Thread 类,那么就不能再继承其他类。另外任务与代码没有分离,当多个线程执行一样的任务时需要多份任务代码,而 Runable 则没有这个限制。
下面看实现 Runnable 接口的 run 方法方式。
public static class RunableTask implements Runnable{@Overridepublic void run() {System.out.println("I am a child thread");}}public static void main(String[] args) throws InterruptedException{RunableTask task = new RunableTask();new Thread(task).start();new Thread(task).start();}
但是上面介绍的两种方式都有一个缺点,就是任务没有返回值。下面看最后一种,即使用 FutureTask 的方式。
//创建任务类,类似 Runablepublic static class CallerTask implements Callable<String>{@Overridepublic String call() throws Exception {return 「hello」;}}public static void main(String[] args) throws InterruptedException {// 创建异步任务FutureTask<String> futureTask = new FutureTask<>(new CallerTask());//启动线程new Thread(futureTask).start();try {//等待任务执行完毕,并返回结果String result = futureTask.get();System.out.println(result);} catch (ExecutionException e) {e.printStackTrace();}}
线程通知与等待
1.wait()函数
当一个线程调用一个共享变量的 wait()方法时,该调用线程会被阻塞挂起,直到发生下面几件事情之一才返回:(1)其他线程调用了该共享对象的 notify()或者 notifyAll()方法;(2)其他线程调用了该线程的 interrupt()方法,该线程抛出 InterruptedException 异常返回。
另外需要注意的是,如果调用 wait()方法的线程没有事先获取该对象的监视器锁,则调用 wait()方法时调用线程会抛出 IllegalMonitorStateException 异常。
那么一个线程如何才能获取一个共享变量的监视器锁呢?
(1)执行 synchronized 同步代码块时,使用该共享变量作为参数。
synchronized(共享变量){//doSomething}
(2)调用该共享变量的方法,并且该方法使用了 synchronized 修饰。
synchronized void add(int a, int b){//doSomething}
另外需要注意的是,一个线程可以从挂起状态变为可以运行状态(也就是被唤醒),即使该线程没有被其他线程调用 notify()、notifyAll()方法进行通知,或者被中断,或者等待超时,这就是所谓的虚假唤醒。
虽然虚假唤醒在应用实践中很少发生,但要防患于未然,做法就是不停地去测试该线程被唤醒的条件是否满足,不满足则继续等待,也就是说在一个循环中调用 wait()方法进行防范。退出循环的条件是满足了唤醒该线程的条件。
synchronized (obj) {while (条件不满足){obj.wait();}}
另外需要注意的是,当前线程调用共享变量的 wait()方法后只会释放当前共享变量上的锁,如果当前线程还持有其他共享变量的锁,则这些锁是不会被释放的。下面来看一个例子。
// 创建资源private static volatile Object resourceA = new Object();private static volatile Object resourceB = new Object();public static void main(String[] args) throws InterruptedException {// 创建线程Thread threadA = new Thread(new Runnable() {public void run() {try {// 获取 resourceA 共享资源的监视器锁synchronized (resourceA) {System.out.println(「threadA get resourceA lock」);// 获取 resourceB 共享资源的监视器锁synchronized (resourceB) {System.out.println(「threadA get resourceB lock」);// 线程 A 阻塞,并释放获取到的 resourceA 的锁System.out.println(「threadA release resourceA lock」);resourceA.wait();}}} catch (InterruptedException e) {e.printStackTrace();}}});// 创建线程Thread threadB = new Thread(new Runnable() {public void run() {try {//休眠 1sThread.sleep(1000);// 获取 resourceA 共享资源的监视器锁synchronized (resourceA) {System.out.println(「threadB get resourceA lock」);System.out.println(「threadB try get resourceB lock...」);// 获取 resourceB 共享资源的监视器锁synchronized (resourceB) {System.out.println(「threadB get resourceB lock」);// 线程 B 阻塞,并释放获取到的 resourceA 的锁System.out.println(「threadB release resourceA lock」);resourceA.wait();}}} catch (InterruptedException e) {e.printStackTrace();}}});// 启动线程threadA.start();threadB.start();// 等待两个线程结束threadA.join();threadB.join();System.out.println(「main over」);}
输出结果如下:

如上代码中,在 main 函数里面启动了线程 A 和线程 B,为了让线程 A 先获取到锁,这里让线程 B 先休眠了 1s,线程 A 先后获取到共享变量 resourceA 和共享变量 resourceB 上的锁,然后调用了 resourceA 的 wait()方法阻塞自己,阻塞自己后线程 A 释放掉获取的 resourceA 上的锁。
线程 B 休眠结束后会首先尝试获取 resourceA 上的锁,如果当时线程 A 还没有调用 wait()方法释放该锁,那么线程 B 会被阻塞,当线程 A 释放了 resourceA 上的锁后,线程 B 就会获取到 resourceA 上的锁,然后尝试获取 resourceB 上的锁。由于线程 A 调用的是 resourceA 上的 wait()方法,所以线程 A 挂起自己后并没有释放获取到的 resourceB 上的锁,所以线程 B 尝试获取 resourceB 上的锁时会被阻塞。
这就证明了当线程调用共享对象的 wait()方法时,当前线程只会释放当前共享对象的锁,当前线程持有的其他共享对象的监视器锁并不会被释放。
当一个线程调用共享对象的 wait()方法被阻塞挂起后,如果其他线程中断了该线程,则该线程会抛出 InterruptedException 异常并返回。
public class WaitNotifyInterupt {static Object obj = new Object();public static void main(String[] args) throws InterruptedException {//创建线程Thread threadA = new Thread(new Runnable() {public void run() {try {System.out.println(「---begin---」);//阻塞当前线程synchronized (obj) {obj.wait();}System.out.println("---end---");} catch (InterruptedException e) {e.printStackTrace();}}});threadA.start();Thread.sleep(1000);System.out.println("---begin interrupt threadA---");threadA.interrupt();System.out.println("---end interrupt threadA---");}}
输出如下。

在如上代码中,threadA 调用共享对象 obj 的 wait()方法后阻塞挂起了自己,然后主线程在休眠 1s 后中断了 threadA 线程,中断后 threadA 在 obj.wait()处抛出 java.lang. InterruptedException 异常而返回并终止。
2.wait(long timeout)函数
该方法相比 wait()方法多了一个超时参数,它的不同之处在于,如果一个线程调用共享对象的该方法挂起后,没有在指定的 timeout ms 时间内被其他线程调用该共享变量的 notify()或者 notifyAll()方法唤醒,那么该函数还是会因为超时而返回。如果将 timeout 设置为 0 则和 wait 方法效果一样,因为在 wait 方法内部就是调用了 wait(0)。需要注意的是,如果在调用该函数时,传递了一个负的 timeout 则会抛出 IllegalArgumentException 异常。
3.wait(long timeout, int nanos)函数
在其内部调用的是 wait(long timeout)函数,如下代码只有在 nanos>0 时才使参数 timeout 递增 1。
public final void wait(long timeout, int nanos) throws InterruptedException {if (timeout < 0) {throw new IllegalArgumentException("timeout value is negative");}if (nanos < 0 || nanos > 999999) {throw new IllegalArgumentException("nanosecond timeout value out of range");}if (nanos > 0) {timeout++;}wait(timeout);}
4.notify() 函数
一个线程调用共享对象的 notify()方法后,会唤醒一个在该共享变量上调用 wait 系列方法后被挂起的线程。一个共享变量上可能会有多个线程在等待,具体唤醒哪个等待的线程是随机的。
此外,被唤醒的线程不能马上从 wait 方法返回并继续执行,它必须在获取了共享对象的监视器锁后才可以返回,因为该线程还需要和其他线程一起竞争该锁,只有该线程竞争到了共享变量的监视器锁后才可以继续执行。
类似 wait 系列方法,只有当前线程获取到了共享变量的监视器锁后,才可以调用共享变量的 notify()方法,否则会抛出 IllegalMonitorStateException 异常。
5.notifyAll() 函数
不同于在共享变量上调用 notify()函数会唤醒被阻塞到该共享变量上的一个线程,notifyAll()方法则会唤醒所有在该共享变量上由于调用 wait 系列方法而被挂起的线程。
下面举一个例子来说明 notify()和 notifyAll()方法的具体含义及一些需要注意的地方,代码如下。
// 创建资源private static volatile Object resourceA = new Object();public static void main(String[] args) throws InterruptedException {// 创建线程Thread threadA = new Thread(new Runnable() {public void run() {// 获取 resourceA 共享资源的监视器锁synchronized (resourceA) {System.out.println(「threadA get resourceA lock」);try {System.out.println(「threadA begin wait」);resourceA.wait();System.out.println(「threadA end wait」);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}});// 创建线程Thread threadB = new Thread(new Runnable() {public void run() {synchronized (resourceA) {System.out.println(「threadB get resourceA lock」);try {System.out.println(「threadB begin wait」);resourceA.wait();System.out.println(「threadB end wait」);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}});// 创建线程Thread threadC = new Thread(new Runnable() {public void run() {synchronized (resourceA) {System.out.println(「threadC begin notify」);resourceA.notify();}}});// 启动线程threadA.start();threadB.start();Thread.sleep(1000);threadC.start();// 等待线程结束threadA.join();threadB.join();threadC.join();System.out.println(「main over」);}
输出结果如下。

从输出结果可知线程调度器这次先调度了线程 A 占用 CPU 来运行,线程 A 首先获取 resourceA 上面的锁,然后调用 resourceA 的 wait()方法挂起当前线程并释放获取到的锁,然后线程 B 获取到 resourceA 上的锁并调用 resourceA 的 wait()方法,此时线程 B 也被阻塞挂起并释放了 resourceA 上的锁,到这里线程 A 和线程 B 都被放到了 resourceA 的阻塞集合里面。线程 C 休眠结束后在共享资源 resourceA 上调用了 notify()方法,这会激活 resourceA 的阻塞集合里面的一个线程,这里激活了线程 A,所以线程 A 调用的 wait()方法返回了,线程 A 执行完毕。而线程 B 还处于阻塞状态。如果把线程 C 调用的 notify()方法改为调用 notifyAll()方法,则执行结果如下。

从输入结果可知线程 A 和线程 B 被挂起后,线程 C 调用 notifyAll()方法会唤醒 resourceA 的等待集合里面的所有线程,这里线程 A 和线程 B 都会被唤醒,只是线程 B 先获取到 resourceA 上的锁,然后从 wait()方法返回。线程 B 执行完毕后,线程 A 又获取了 resourceA 上的锁,然后从 wait()方法返回。线程 A 执行完毕后,主线程返回,然后打印输出。
一个需要注意的地方是,在共享变量上调用 notifyAll()方法只会唤醒调用这个方法前调用了 wait 系列函数而被放入共享变量等待集合里面的线程。
等待线程执行终止的 join 方法
在项目实践中经常会遇到一个场景,就是需要等待某几件事情完成后才能继续往下执行,比如多个线程加载资源,需要等待多个线程全部加载完毕再汇总处理。Thread 类中有一个 join 方法就可以做这个事情,join 方法是 Thread 类直接提供的。join 是无参且返回值为 void 的方法。
public static void main(String[] args) throws InterruptedException {Thread threadOne = new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(「child threadOne over! 」);}});Thread threadTwo = new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(「child threadTwo over! 」);}});//启动子线程threadOne.start();threadTwo.start();System.out.println(「wait all child thread over! 」);//等待子线程执行完毕,返回threadOne.join();threadTwo.join();System.out.println("all child thread over! ");}
另外,线程 A 调用线程 B 的 join 方法后会被阻塞,当其他线程调用了线程 A 的 interrupt()方法中断了线程 A 时,线程 A 会抛出 InterruptedException 异常而返回。下面通过一个例子来加深理解。
public static void main(String[] args) throws InterruptedException {//线程 oneThread threadOne = new Thread(new Runnable() {@Overridepublic void run() {System.out.println(「threadOne begin run! 」);for (; ; ) {}}});//获取主线程final Thread mainThread = Thread.currentThread();//线程 twoThread threadTwo = new Thread(new Runnable() {@Overridepublic void run() {//休眠 1stry {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}//中断主线程mainThread.interrupt();}});// 启动子线程threadOne.start();//延迟 1s 启动线程threadTwo.start();try{//等待线程 one 执行结束threadOne.join();}catch(InterruptedException e){System.out.println(「main thread:」 + e);}}
输出结果如下。

这里需要注意的是,在 threadTwo 里面调用的是主线程的 interrupt()方法,而不是线程 threadOne 的。
