Java 并发:第二部分

眉间皱痕 提交于 2020-01-07 07:18:49

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

在了解了 如何创建线程之后,我们将会在本篇文章中了解可以对多线程做什么。
当我们有了线程,我们就可以对线程做如下几個操作:
1、让当前线程在x毫秒的时间内睡眠;
2、等待壹個其它的线程结束;
3、管理线程的优先级,暂停壹個线程以给予壹個其它线程运行的机会;

4、中断线程;

我们看看该如何去做所有这些事情。

首先简单点,我们可以让壹個线程在指定数量的毫秒内睡眠。为了做到这壹点,Thread类有壹個方法sleep(long millis)。但是这個方法是静态的,所以你只能让当前线程进入睡眠状态。你不能选择那個你希望它睡眠的线程,你唯壹的选择是当前线程:

Thread.sleep(1000);
让当前线程睡眠1000毫秒(也就是1秒)。但是,你必须捕获异常,InterruptedException。如果睡眠的线程被中断,这個异常就会发生,所以你可以这样做:
try {
	Thread.sleep(1000);
} catch (InterruptedException e){
	e.printStackTrace();
}
但是这不是壹個好的管理异常的方法,过壹会儿我们将会看到如何处理这個异常。
如果你希望更加精确,你可以使用sleep()方法的重载版本,带有两個参数毫秒和纳秒的sleep()方法。这個睡眠时间的精确度依赖于系统时钟和计时器。
举個例子,如果你希望睡眠1000毫秒1000纳秒,你可以这样做:
try {
	Thread.sleep(1000, 1000);
} catch (InterruptedException e){
	e.printStackTrace();
}
这里有個小例子来测试上述代码:
public class SleepThread {
    public static void main(String[] args) {
        System.out.println("Current time millis : " + System.currentTimeMillis());

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Current time millis : " + System.currentTimeMillis());

        System.out.println("Nano time : " + System.nanoTime());

        try {
            Thread.sleep(2, 5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Nano time : " + System.nanoTime());
    }
}
在我的电脑上,上述代码的运行结果为:
Current time millis : 1273959308480
Current time millis : 1273959309480
Nano time : 5878165216075
Nano time : 5878166730976

你可以看到基于毫秒的睡眠时间非常精确,但是基于纳秒的睡眠时间粗糙的多。当然,运行结果依赖于你的电脑、你的操作系统和你的配置。
另一方面,你可以在线程中等待着其他线程死亡。例如,您可以创建五个线程来计算各部分的结果,等这五個线程完成后,基于五個线程的结果计算最终的结果。这样做,你可以使用线程类的join()方法。这种方法不是静态的,所以你可以用在任何线程中等待它死亡。当线程在等待另一个线程中断时,在sleep()这种方法中会抛出InterruptedException异常。所以为了等待线程2,你必须这样做:

try {
	thread2.join();
} catch (InterruptedException e){
	e.printStackTrace();
}


这将迫使当前线程等待 thread2 死亡。你也可以增加超时时间,使用join(),join(long millis) 和join(long millis, int nanos)方法的重载版本,以毫秒、或者毫秒+纳秒为单位,这里有個小例子演示了所有的用法。

public class JoinThread {
	public static void main(String[] args) {
		Thread thread2 = new Thread(new WaitRunnable());
		Thread thread3 = new Thread(new WaitRunnable());

		System.out.println("Current time millis : " + System.currentTimeMillis());

		thread2.start();

		try {
			thread2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		System.out.println("Current time millis : " + System.currentTimeMillis());

		thread3.start();

		try {
			thread3.join(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		System.out.println("Current time millis : " + System.currentTimeMillis());
	}

	private static class WaitRunnable implements Runnable {
		@Override
		public void run() {
			try {
				Thread.sleep(5000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
        }
}
以上代码在我的电脑上运行的结果如下:
Current time millis : 1274015478535
Current time millis : 1274015483538
Current time millis : 1274015484538

你可以看到第壹個 join() 等待了另壹個线程 5 秒钟,当我们设置了壹個超时时,我们只等待了壹秒钟就从 join 方法中返回了。
当我们使用线程时,同样有可能更改线程的优先级。在Java虚拟机中,Thread类使用基于优先级的调度算法。所以如果壹個线程以更高的优先级进入运行态时,新的线程将会运行,而当前正在运行的线程会返回可运行态,并等待下次运行。但是这种行为是无法保证的,它完全依赖于你使用的虚拟机。所以,不要依赖线程的优先级,只使用它来提高你的程序的性能。
通常来讲,线程类的优先级是壹個从0到10的整数,但是有些虚拟机拥有更低或者更高的优先级。为了了解优先级的范围,你可以使用线程类的常量:

public class ThreadPriorityRange {
	public static void main(String[] args) {
		System.out.println("Minimal priority : " + Thread.MIN_PRIORITY);
		System.out.println("Maximal priority : " + Thread.MAX_PRIORITY);
		System.out.println("Norm priority : " + Thread.NORM_PRIORITY);
         }
}
在我的机器上,我总是得到如下结果:
Minimal priority : 1
Maximal priority : 10
Norm priority
如果要设置线程的优先级,你可以使用 Thread 类的 setPriority(int priority) 方法。如果你传入壹個比最大优先级更大的值,这個方法就会使用最大优先级对应的值。如果你没有指定优先级,就会默认使用当前线程的优先级。
另外壹种会用到的与优先级相关的是 yield() 方法,这個方法是静态的,所以它作用于当前线程。这個方法的作用就是让线程再次进入运行状态,其它线程只能再次等待他们运行的机会。但是在实践中,这個方法的行为是无法保证的。在特定系统中,它可以实现为壹個空操作。在实践中想测试这個并不容易,因为它的运行结果真的是依赖于你的电脑,虚拟机和操作系统。实践中最好不使用线程的优先级。
最后壹件你可以使用线程类做的事情是中断它。在Java中,如果壹個线程没有结束,你没有办法强制它停止,它将继续无限的执行下去。但是你可以使用 Thread 类的 interrupt() 方法中断它。这個方法中断壹個线程,如果线程在睡觉或者正在加入另壹個线程,方法就会抛出壹個InterruptedException。你必须知道如果线程在睡觉或者加入另壹個线程,线程的中断状态会被清除。顾名思义,isInterrupted()方法会返回false。以下是壹個演示的小例子:
public class InterruptThread {
	public static void main(String[] args) {
		Thread thread1 = new Thread(new WaitRunnable());

		thread1.start();

		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		thread1.interrupt();
	}

	private static class WaitRunnable implements Runnable {
		@Override
		public void run() {
			System.out.println("Current time millis : " + System.currentTimeMillis());

			try {
				Thread.sleep(5000);
			} catch (InterruptedException e) {
				System.out.println("The thread has been interrupted");
				System.out.println("The thread is interrupted : " + Thread.currentThread().isInterrupted());
			}

			System.out.println("Current time millis : " + System.currentTimeMillis());
		}
        }
}
运行后产生了这样的结果:
Current time millis : 1274017633151
The thread has been interrupted
The thread is interrupted : false
Current time millis : 1274017634151
你可以看到壹秒钟之后,第二個线程被中断,它的中断状态被设置为false。如果你没有睡眠,但是做了很多繁重的工作,你可以像下面这样去测试中断,以促使你的线程正确的中断。
public class InterruptableRunnable implements Runnable {
	@Override
	public void run() {
		while(!Thread.currentThread().isInterrupted()){
			//Heavy operation
		}
	}
}
现在你知道如何中断壹個线程了,你可以想象,这种简单的捕获InterruptedException不足以让你的线程“安全中断”。想象你的线程像下面这样:
public class UglyRunnable implements Runnable {
	@Override
	public void run() {
		while(!Thread.currentThread().isInterrupted()){
			//Heavy operation
			try {
				Thread.sleep(5000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//Other operation
		}
	}
}
现在,当你的线程在睡眠时,另外壹個线程想中断你的线程。睡眠会被中断,但是中断状态会被清除以便循环可以继续。创建壹個更好的线程的方法是,在捕获InterruptedException异常之后,再次中断线程:
public class BetterRunnable implements Runnable {
	@Override
	public void run() {
		while(!Thread.currentThread().isInterrupted()){
			//Heavy operation
			try {
				Thread.sleep(5000);
			} catch (InterruptedException e) {
				Thread.currentThread().interrupt();
			}
			//Other operation
		}
	}
}

使用这段代码,在中断之后,中断状态会被恢复,循环会停止。基于你的代码,你也可以在中断之后,在interrupt()方法后增加壹個 continue 语句来确保不再操作。在某些情况下,你还需要使用几個if语句来检测中断状态,以控制其做或者不做某些事情。
所以,我们现在已经知道了所有用线程可以做的事情了。我希望你们觉得这篇文章有趣。你可以在这里下载本文的源代码。
下壹篇文章是关于Java并发的,我们将会看到如何使用同步代码来保证线程安全。

本文英文原文:http://www.baptiste-wicht.com/2010/05/java-concurrency-part-2-manipulate-threads/

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!