I. 简介
线程机制是开发中非常重要、也是非常复杂的一个环节. 那么为什么需要线程?
首先,在程序的执行中,不可避免地会遇到一些需要等待的任务,比如从数据库请求数据、做一些耗时操作等. 而Java 语言本身的任务处理机制是顺序控制流,也就是说,把所有任务排成一个队列,只有第一个任务执行完毕,第二个任务才能执行,第三个任务则要等第二个任务完成…
如果没有多线程的加入,那用户在使用程序时,就只能在某些时候等待一些操作完成,这势必会影响用户体验. 因此,线程的加入,大大地提升了程序的实用性,但同时也增加了程序设计的难度.
II. 线程实质
关于线程需要注意的一点是,即使是在多核处理器上运行,线程机制其实是在单个处理器上交替着完成不同的任务. 比如现在有Task A, Task B 和Task C;这三个任务现在用Java 的多线程机制来解决,情况将会是这样:CPU 给Task A分配了一个小时间块来执行Task A,如果Task A没有完成,那么CPU 现在也会转移到处理Task B上,并给Task B分配同样长度的时间块,并定时切换到处理Task C 去,然后再分给Task C 的时间结束后,再次返回处理Task A,如此循环,直到处理完所有的任务. 但因为CPU 自身的速度够快,会给人一种多个任务在同时执行的错觉.
III. Runnable
介绍完了线程内部的运作方法,下面首先会讲解Runnable 接口.
Runnable 接口自身是不参与线程分配和调度的,它自身的存在目的其实是规定某个线程内的任务要如何执行.
public class Runnables implements Runnable {
private int countdown = 5;
private int id;
public Runnables(int id) {
this.id = id;
}
public String status() {
return id + " (" + (countdown > 0 ? countdown: "finished")
+ "), ";
}
@Override
public void run() {
while (--countdown > 0) {
System.out.print(status());
}
}
public static void main(String [] args) {
for (int i = 0; i < 5; i++) {
Runnables runnables = new Runnables(i);
runnables.run();
System.out.println();
}
}
}
在这个例子中,Runnables 执行了Runnable 接口,并重写了run()
方法;某个task 需要执行的操作,便是写在run()
的.
但由于Runnable 接口本身不具备分发和调用线程的能力,所以所使用的都是同一个线程,即系统分发给main()
函数的线程,并且每个任务都是等上一个任务执行完后再执行的.
而这一点也可以从返回值中看出.
output://
0(4), 0(3), 0(2), 0(1),
1(4), 1(3), 1(2), 1(1),
2(4), 2(3), 2(2), 2(1),
3(4), 3(3), 3(2), 3(1),
4(4), 4(3), 4(2), 4(1),
IV. Thread
如果说Runnable 接口是用来规定一个线程中的任务,那么Thread 类便是分发、调度线程的工具了.
在下面的示例中,使用了Thread 来执行 Runnables 类.
public static void main(String [] args) {
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(new Runnables(i));
thread.start();
System.out.println();
}
}
在新建了一个Thread 类对象后,调用start()
方法开始线程. 而每一个线程将会在什么时候运行是无法知道的. 打印台的输出便很好的说明了这一点.
output://
0(4), 0(3), 0(2), 0(1),
1(4), 1(3), 1(2), 1(1), 2(4), 2(3), 2(2), 2(1),
3(4), 3(3), 3(2), 3(1),
4(4), 4(3), 4(2), 4(1),
Executor
Executor 能代替开发者管理Thread 对象,从而简化开发过程. 它是客户端与任务执行之间的一个简介层.
public static void main(String [] args) {
ExecutorService ex = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++)
ex.execute(new Runnables(i));
ex.shutdown();
}
通过创建Executor 的对象,并调用execute()
方法,就可以启动一个线程了. 而shutdown()
在调用后,将不可通过ex
这个对象创建新的线程了.
同时,这里获得ExecutorService 对象的,是Executors 的方法. 通常会使用newCachedThreadPool()
,但如果需要规定线程数量,可调用newFixedThreadPool()
并传入参数指定线程数,或者调用newSingleThreadExecutor()
返回只能启动一条线程的Executor.
V. Callable
如果需要任务在完成后,返回一个值,就需要使用Callable 而不是Runnable 接口了. Callable 接口需要执行call()
方法,并返回一个值.
public class Callabel implements Callable<String> {
private static int count = 0;
private int id = ++count;
public Callabel() {}
@Override
public String call() throws Exception {
return Integer.toString(id);
}
public static void main(String [] args) {
ExecutorService ex = Executors.newCachedThreadPool();
ArrayList<Future<String>> resutls = new ArrayList<>();
for (int i = 0; i < 5; i++) {
resutls.add(ex.submit(new Callabel()));
}
for (Future<String> result: resutls) {
try {
// get() blocks until completion
System.out.println(result.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
finally {
ex.shutdown();
}
}
}
}
这里返回了一个String 对象,并通过类型为Future< String >的数组把结果保存下来了. 也就是说,如果想要获得Callable 返回的数值,需要一个Future 对象来保存,并通过get()
方法来获取值.
VI. Thread 其他方法
TimeUnit sleep() 休眠
使用TimeUnit 的sleep()
方法可以让线程休眠,时间取决于参入的参数.
public class Sleep implements Runnable{
private static int count = 0;
private final int id = ++count;
@Override
public void run() {
try {
System.out.print(id + "a ");
TimeUnit.SECONDS.sleep(2);
System.out.print(id + "b ");
System.out.println();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String [] args) {
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
executor.execute(new Sleep());
}
}
}
此处,每个线程会休眠2秒.
Thread.yield() 让步
让步的意思是说:我的任务执行完了,现在我将CPU让步给其他线程.
public class Yield implements Runnable{
private static int count = 0;
private final int id = ++count;
@Override
public void run() {
int sum = 0;
for (int i = 0; i < 10; i++) {
sum += i;
if (sum > 10) {
System.out.println("id:" + id + " ==> " + sum);
Thread.yield();
}
}
}
public static void main(String [] args) {
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
executor.execute(new Yield());
}
}
}
output://
id:1 ==> 15
id:3 ==> 15
id:2 ==> 15
id:4 ==> 15
id:5 ==> 15
id:4 ==> 21
id:3 ==> 21
id:1 ==> 21
id:4 ==> 28
id:5 ==> 21
id:2 ==> 21
id:5 ==> 28
id:2 ==> 28
id:5 ==> 36
id:4 ==> 36
id:1 ==> 28
id:3 ==> 28
id:4 ==> 45
id:5 ==> 45
id:2 ==> 36
id:3 ==> 36
id:1 ==> 36
id:3 ==> 45
id:1 ==> 45
id:2 ==> 45
每个线程在启动后,开始执行加法运算. 当sum大于10时,线程会暂停当前任务,将CPU 让给下一个线程. 所以从前5个输出中可以看到,线程都是在sum 到达15后暂停的进程.
Thread setPriority() 优先级
尽管各个线程的执行是随机的,但CPU 总会被优先分配给优先级最高的线程执行任务.
public class Priority implements Runnable {
private static int count = 0;
private final int id = ++count;
@Override
public void run() {
int sum = 0;
for (int i = 0; i < 10; i++) {
sum += i;
if (sum > 10) {
System.out.println("id:" + id + " ==> " + sum);
Thread.yield();
}
}
}
public static void main(String [] args) {
for (int i = 1; i <= 10; i++) {
Thread thread = new Thread(new Priority());
thread.setPriority(i);
thread.start();
}
}
}
output://
id:1 ==> 15
id:4 ==> 15
id:1 ==> 21
id:6 ==> 15
id:3 ==> 15
id:2 ==> 15
id:6 ==> 21
id:7 ==> 15
id:1 ==> 28
id:4 ==> 21
id:10 ==> 15
id:5 ==> 15
id:10 ==> 21
id:4 ==> 28
id:1 ==> 36
id:7 ==> 21
id:6 ==> 28
id:9 ==> 15
id:3 ==> 21
id:2 ==> 21
id:8 ==> 15
id:2 ==> 28
id:9 ==> 21
id:3 ==> 28
id:6 ==> 36
id:7 ==> 28
id:1 ==> 45
id:4 ==> 36
id:10 ==> 28
id:5 ==> 21
id:4 ==> 45
id:7 ==> 36
id:6 ==> 45
id:3 ==> 36
id:9 ==> 28
id:2 ==> 36
id:8 ==> 21
id:9 ==> 36
id:3 ==> 45
id:7 ==> 45
id:5 ==> 28
id:10 ==> 36
id:5 ==> 36
id:9 ==> 45
id:8 ==> 28
id:2 ==> 45
id:8 ==> 36
id:5 ==> 45
id:10 ==> 45
id:8 ==> 45
线程优先级按照1 到10排列(与平台有关),1 的优先级最高.
通常在操作平台未知的情况下,会使用Thread.MAX_PRIORITY, Thread.MIN_PRIORITY, 和 Thread.NORM_PRIORITY.