编程中很大部分的问题只需要使用顺序编程来解决,然而,对于某些问题,需要并发的同时执行程序中多个部分则是十分方便且有必要的,尤其是在多处理器的环境中,多个部分的代码可以实际上同时执行可以极大地提高程序的执行速度,同时也可以提供更加易用的模型
然而,当你企图使用并发执行任务进行编程,问题也会接踵而至,并发程序的同步问题,内存空间的共享问题等等,因此并发有时是危险的
java 的并发是通过多线程机制实现的,每一个子任务都将有一个执行线程驱动,一个线程就是在进程中的单一顺序控制流
java.lang 包提供了三种实现多线程开发的工具:
- Callable 接口
- Runnable 接口
- Thread 类
他们的区别在于:
- Callable 任务执行后可以具有返回值,在任务执行过程中也可以抛出异常,而 Runnable 和 Thread 都不行
- 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果,而 Runable 和 Thread 都不行
- Thread 是通过实现 Runnable 接口实现的,因此可以认为 Thread 是 Runnable 的子类
下面的例子实现了 Runnable 最基本的用法:
package com.techlog.test;
public class Test implements Runnable {
protected int countDown = 10;
private static int taskCount = 0;
private final int id = taskCount++;
public Test() {}
public Test(int count) {
countDown = count;
}
public String status() {
return "#" + id + "(" + (countDown > 0 ? countDown : "Liftoff!!!") + "), ";
}
@Override
public void run() {
while(countDown-- > 0) {
System.out.print(status());
Thread.yield();
}
}
public static void main(String[] args) {
Test lauch = new Test();
lauch.run();
System.out.println();
}
}
虽然上例中实际上只启动了一个线程进行工作,事实上,他已经展示了并发编程的方法
通常在任务的 run 方法中总会有某种形式的循环
要让多个 Runnable 并发运行就需要使用 Thread 类,Thread 类通过 Runnable 对象创建,他实现了 Runnable,可以看做是 Runnable 的子类,在调用 Thread 对象的 start 方法后,Thread 通过调用 Runnable 对象中的 run 方法创建线程实现并发
start 并不会等到所有任务全部执行完成后返回,而是会在调用后立即返回
以 Runnable 对象为参数创建
package com.techlog.test;
public class Test implements Runnable {
protected int countDown = 10;
private static int taskCount = 0;
private final int id = taskCount++;
public Test() {}
public Test(int count) {
countDown = count;
}
public String status() {
return "#" + id + "(" + (countDown > 0 ? countDown : "Liftoff!!!") + "), ";
}
@Override
public void run() {
while(countDown-- > 0) {
System.out.print(status());
Thread.yield();
}
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(new Test()).start();
}
System.out.print("Waiting for LiftOff, ");
}
}
输出了:
#0(9), #3(9), #4(9), #3(8), #2(9), Waiting for LiftOff, #1(9), #2(8), #3(7), #4(8), #3(6), #4(7), #0(8), #3(5), #4(6), #2(7), #1(8), #4(5), #3(4), #4(4), #0(7), #3(3), #1(7), #2(6), #3(2), #0(6), #4(3), #4(2), #3(1), #2(5), #3(Liftoff!!!), #1(6), #2(4), #4(1), #0(5), #2(3), #4(Liftoff!!!), #1(5), #1(4), #2(2), #0(4), #2(1), #0(3), #1(3), #0(2), #1(2), #2(Liftoff!!!), #0(1), #1(1), #0(Liftoff!!!), #1(Liftoff!!!),
显然,启动了五个线程并发执行各自的计时工作,同时,也展示了 start 方法的立即返回
Thread.yield 静态方法在线程内部执行,表示主动放弃一轮 CPU,让出时间片给其他线程工作
直接继承 Thread 类实现
最简单的多线程任务创建的方法其实是直接让你的类继承自 Thread 类
package com.techlog.test;
public class Test extends Thread {
private int countDown = 5;
private static int threadCount = 0;
public Test() {
super(Integer.toString(++threadCount));
start();
}
public String toString() {
return "#" + getName() + "(" + countDown + "), ";
}
public void run() {
while (true) {
System.out.print(this);
if (--countDown == 0) {
return;
}
}
}
public static void main(String[] argv) {
for (int i = 0; i < 10; ++i) {
new Test();
}
}
}
打印出了:
#1(5), #4(5), #3(5), #2(5), #6(5), #3(4), #4(4), #5(5), #1(4), #8(5), #5(4), #4(3), #9(5), #3(3), #7(5), #6(4), #2(4), #6(3), #7(4), #10(5), #3(2), #9(4), #4(2), #5(3), #8(4), #1(3), #8(3), #5(2), #4(1), #9(3), #3(1), #10(4), #7(3), #6(2), #2(3), #6(1), #7(2), #10(3), #9(2), #5(1), #8(2), #1(2), #8(1), #9(1), #10(2), #7(1), #10(1), #2(2), #1(1), #2(1),
正如前面所述 Runnable 并不返回任何返回值,也不能在执行过程中抛出异常,实现这两个功能需要使用 Callable 泛型接口,它使用 call 方法返回值
Callable 接口是使用 ExecutorService.submit 方法调用的
package com.techlog.test;
import java.util.ArrayList;
import java.util.concurrent.*;
class TaskWithResult implements Callable<String> {
private int id;
public TaskWithResult(int id) {
this.id = id;
}
@Override
public String call() throws Exception {
return "result of TaskWithResult " + id;
}
}
public class Test {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
ArrayList<Future<String>> results = new ArrayList<>();
for (int i = 0; i < 10; ++i) {
results.add(exec.submit(new TaskWithResult(i)));
}
for (Future<String> fs : results) {
try {
System.out.println(fs.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
exec.shutdown();
}
}
}
}
打印出了:
result of TaskWithResult 0
result of TaskWithResult 1
result of TaskWithResult 2
result of TaskWithResult 3
result of TaskWithResult 4
result of TaskWithResult 5
result of TaskWithResult 6
result of TaskWithResult 7
result of TaskWithResult 8
result of TaskWithResult 9
ExecutorService 的 shutdown 方法表示此后所有加入线程池的线程都将不会被执行,已经加入线程池的线程则会持续运行不会受到影响
通过 Thread 对象的 setDaemon 方法,可以将线程变成后台(参数置为 true)或前台(参数置为 false)线程
必须在线程启动之前调用 setDaemon 方法,才能把它置为后台线程
package com.techlog.test;
import java.util.concurrent.TimeUnit;
public class Test implements Runnable {
@Override
public void run() {
while (true) {
try {
TimeUnit.MILLISECONDS.sleep(100);
System.out.println(Thread.currentThread() + " " + this);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] argv) throws InterruptedException {
for (int i = 0; i < 10; i++) {
Thread daemon = new Thread(new Test());
daemon.setDaemon(true);
daemon.start();
}
System.out.println("All daemons started");
TimeUnit.MILLISECONDS.sleep(175);
}
}
上面的程序展示了后台线程的创建和执行的方法
一旦后台线程启动,他就会脱离主线程执行,因此主线程不会等待所有线程执行完成就会退出
因此如果最后不让主线程沉睡一定的时间,我们将无法看到后台线程执行时的输出
后台线程创建的子线程也将会是后台线程
Thread 还提供了 join 方法,即在线程池中加入当前线程
package com.techlog.test;
class Sleeper extends Thread {
private int duration;
public Sleeper(String name, int sleepTime) {
super(name);
duration = sleepTime;
start();
}
public void run() {
try {
sleep(duration);
} catch (InterruptedException e) {
System.out.println(getName() + " was interrupted." + isInterrupted());
return;
}
System.out.println(getName() + " has awakened");
}
}
class Joiner extends Thread {
private Sleeper sleeper;
public Joiner(String name, Sleeper sleeper) {
super(name);
this.sleeper = sleeper;
start();
}
public void run() {
try {
sleeper.join();
System.out.println(getName() + " join completed");
} catch (InterruptedException e) {
System.out.println(getName() + " was interrupted." + isInterrupted());
}
}
}
public class Test {
public static void main(String[] argv) {
Sleeper sleepy = new Sleeper("Sleepy", 1500);
Sleeper grumpy = new Sleeper("Grumpy", 1500);
Joiner dopey = new Joiner("Dopey", sleepy);
Joiner doc = new Joiner("Doc", grumpy);
grumpy.interrupt();
}
}
打印出了:
Grumpy was interrupted.false
Doc join completed
Sleepy has awakened
Dopey join completed
可以看到,Sleepy 和 Grumpy 创建后即开始睡眠,而主线程继续执行,直到调用 grumpy.interrupt 将 Grumpy 中断
被中断的 Grumpy 线程随后执行了加入的 Doc 线程,而随着 Sleepy 的沉睡,join 的 Dopey 线程并没有得以执行
可见,睡眠的线程会持续交出 CPU 控制权,只有当他醒来或中断时才可以被 join
技术帖
龙潭书斋
线程
thread
线程池
并发
java
thinking in java
java编程思想
callable
runnable