编程问题中的至关一大部分均可以经过顺序编程来解决。然而,对于某些问题,若是可以并行的执行程序中的多个部分,则会变得很是方便甚至很是必要,这些部分要么能够并发执行,要么在多处理器环境下能够同时执行。java
并发编程可使程序执行速度获得极大的提升,或者为设计某些类型的程序提供更简单的模型。学习并发编程就像进入一个全新的领域,有点相似于学习一门新的编程语言,或者是学习一整套新的语言概念。要理解并发编程与理解面向对象编程差很少。要想真正的掌握它的实质,就须要深刻的学习和理解。程序员
并发编程使人困惑的一个重要缘由是:使用并发时须要解决的问题有多个,而实现并发的方式也有多种,而且这二者之间没有明显的映射关系。所以咱们必须理解全部的这些问题和特例,以便有效的使用并发。编程
若是你想让一个程序运行的更快,那么能够将其断开为多个片断,在单独的处理器上运行每一个片断。并发是用于多处理器编程的基本工具。当前速度的提升是以多核处理器的形式而不是更快的芯片的形式出现的。为了使程序运行的更快,你必须学习如何利用这些额外的处理器,而这正是并发赋予你的能力。windows
可是,并发一般是提升运行在单处理器上的程序的性能。bash
听起来好像有什么不对。思考一下在单处理器上运行的并发编程开销确实比改程序的全部部分都顺序执行的开销大,由于并发增长了上下文的切换的代价。表面上看,将程序的全部部分当作单个的任务运行好像是开销更小一点。可是咱们要考虑到阻塞的问题。若是程序中的某个任务由于程序控制范围以外的某个条件而致使不能继续执行,那么咱们就说这个任务或线程阻塞了。若是没有并发,则整个程序都将停下来,直至外部条件发生变化。可是,若是使用了并发来编写程序,那么当一个任务阻塞时,程序中的其余任务还能够继续执行,所以这个程序能够保持继续向前执行。事实上,从性能角度看,若是没有任务会阻塞,那么在单处理器机器上使用并发就没有任何意义。多线程
Java 的线程机制是抢占式的,这表示调度机制会周期性地中断线程,将上下文切换到另外一个线程,从而为每一个线程都提供时间片断,使得每一个线程都分配到数量合理的时间去驱动它的任务。在协做式系统中,每一个任务都会自动的放弃控制,这要求程序员有意识的在每一个任务中插入让步语句。协做系统的优点是双重的:上下文切换的开销比抢占式要低廉的多,能够同时执行的线程数量理论上没有限制。当你处理大量的仿真元素时,这是一种理想的解决方案。可是注意,某些协做式系统并未设计为能够在多个处理器之间分配任务,这可能会很是有限。并发
并发须要付出代价,但这些你们与在程序设计、资源负载均衡以及用户方便方面的改进相比,就显得微不足道。一般,线程可以使咱们建立更加松耦合的设计。负载均衡
并发编程使得咱们能够将程序划分为多个分离的、独立的任务。经过使用多线程机制,这些独立任务中的每个都将由执行程序来驱动。一个线程就是进程中的一个单一的顺序控制流,所以,单个进程能够拥有多个并发执行的任务,可是你的程序使得每一个任务都好像有其本身的 CPU 同样。其底层机制是切分 CPU 时间,但咱们一般不须要考虑他。异步
线程模型为编程带来了便利。它简化了在单一程序中同时多个操做的处理。在使用线程时,CPU 将轮流给每一个任务分配其占用时间。每一个人物都以为本身在一直占用 CPU,但事实上 CPU 时间是划分片断分配给了全部任务(也有多是运行是多个 cpu 之上)。线程的一大好处是可使你从这个层次抽身出来,即代码不须要知道它是运行在一个仍是多个 CPU 上。因此,使用线程机制是一个创建透明的,可扩展的程序的方法,若是程序运行速度太慢,为机器增添一个 CPU 就很容易的增长程序运行的速度。多个任务,多个线程是使用多处理器系统的最合理方式。编程语言
线程能够驱动任务,所以须要一种描述任务的方式,这能够由 Runnable 接口来提供。要想定义任务,只须要实现 Runnable 接口并编写 run() 方法,使得该任务能够执行你的命令。
public class LiftOff implements Runnable{
protected int countDown = 10; // Default
private static int taskCount = 0;
private final int id = taskCount++;
public LiftOff() {}
public LiftOff(int countDown) {
this.countDown = countDown;
}
public String status() {
return "#" + id + "(" +
(countDown > 0 ? countDown : "Liftoff!") + "), ";
}
public void run() {
while(countDown-- > 0) {
System.out.print(status());
Thread.yield();
}
}
}
复制代码
任务的 run() 方法一般会有某种形式的循环,使得任务一直运行下去直到再也不须要,因此要设定跳出循环的条件。在 run() 方法中对静态方法 Thread.yield() 的调用是对线程调度器的一种建议,线程调度器是 Java 多线程机制的一部分,能够将 cpu 从一个线程转移到另外一个线程。它声明了,咱们已经执行完生命周期中最重要的一部分,此刻正是切换给其余任务执行的大好时机。
下面示例,任务的 run() 在 main() 方法中直接被调用:
public class MainThread {
public static void main(String[] args) {
// TODO Auto-generated method stub
LiftOff liftOff = new LiftOff();
liftOff.run();
}
}
复制代码
执行结果:
#0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(Liftoff!),
复制代码
从 Runnable 导出一个类,他必须实现 run() 方法,他没有任何内在线程的能力。要实现线程的行为,必须显式的给一个任务赋予它。
将 Runnable 对象转变为一个工做任务的方式是把它提交给一个 Thread 构造器,下面是示例:
public class BasicThreads {
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread thread = new Thread(new LiftOff());
thread.start();
System.out.println("任务开始");
}
}
复制代码
执行结果:
任务开始
#0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(Liftoff!),
复制代码
Thread 构造器只须要一个 Runnable 对象。调用 start() 方法为该线程执行提供必须的初始化操做,而后调用 Runnable 的 run() 方法,以便在这个线程中启动任务。咱们看到输出语句先输出了,任务的语句后输出了。这代表 start() 语句直接返回了。实际上只是产生了对 LiftOff.run() 方法的调用,而且这个方法尚未完成,可是因为 run() 方法是由不一样的线程执行的,因此 main() 方法中的任务还能够继续执行。所以,程序会同时运行两个方法。
下面示例添加更多的任务执行:
public class MoreBasicThreads {
public static void main(String[] args) {
for(int i = 0; i < 5; i++)
new Thread(new LiftOff()).start();
System.out.println("Waiting for LiftOff");
}
}
复制代码
执行结果:
Waiting for LiftOff
#0(9), #1(9), #2(9), #3(9), #4(9), #0(8), #1(8), #2(8), #3(8), #4(8), #0(7), #1(7), #2(7), #3(7), #4(7), #0(6), #1(6), #2(6), #3(6), #4(6), #0(5), #1(5), #2(5), #3(5), #4(5), #0(4), #1(4), #2(4), #3(4), #4(4), #0(3), #1(3), #2(3), #3(3), #4(3), #0(2), #1(2), #2(2), #3(2), #4(2), #0(1), #1(1), #2(1), #3(1), #4(1), #0(Liftoff!), #1(Liftoff!), #2(Liftoff!), #3(Liftoff!), #4(Liftoff!),
复制代码
输出结果说明不一样任务的执行被混在了一块儿。这种交换是由线程调度器自动控制的。若是你有多个处理器,线程调度器就会在这些处理器之间分发线程。当 main() 建立 Thread 对象时,它并无捕获任何对这些对象的引用。在使用普通对象时,对于垃圾回收器是一种公平的游戏,可是在使用 Thread 时,状况就不一样。每一个 Thread 都注册了本身,存在一个对它的引用,并且在任务退出 run() 死亡以前,垃圾回收器没法清楚它。
Java SE5 的 java.util.concurrent 包中的执行器 (Executor) 将为你管理 Thread 对象,简化了并发编程。Executor 在客户端和任务之间创建了一个中间层;与客户端直接执行任务不一样,这个中介将直接执行任务。Executor 容许你管理异步任务的执行,而无需显示的管理线程和生命周期。咱们可使用 Executor 来替代在上个示例中显示的建立 Thread 对象。
public class CachedThreadPool {
public static void main(String[] args) {
// TODO Auto-generated method stub
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 3; i++) {
executorService.execute(new LiftOff());
}
executorService.shutdown();
}
}
复制代码
执行结果:
#2(9), #1(9), #0(9), #2(8), #2(7), #2(6), #2(5), #1(8), #1(7), #2(4), #0(8), #2(3), #2(2), #1(6), #2(1), #0(7), #2(Liftoff!), #1(5), #0(6), #1(4), #0(5), #0(4), #1(3), #0(3), #1(2), #0(2), #1(1), #0(1), #1(Liftoff!), #0(Liftoff!),
复制代码
shutdown() 方法的调用能够防止新任务被提交给这个 Executor ,当前线程将继续运行在 shutdown() 被提交以前提交的全部任务。
咱们可使用不一样类型的 Executor。
public class CachedThreadPool {
public static void main(String[] args) {
// TODO Auto-generated method stub
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 2; i++) {
executorService.execute(new LiftOff());
}
executorService.shutdown();
}
}
复制代码
执行结果:
#0(9), #1(9), #0(8), #1(8), #0(7), #1(7), #1(6), #1(5), #1(4), #1(3), #1(2), #1(1), #0(6), #0(5), #0(4), #1(Liftoff!), #0(3), #0(2), #0(1), #0(Liftoff!),
复制代码
FixedThreadPool 使用了有限的线程集来执行提交的任务,你能够一次性预先执行代价高昂的线程分配,也能够限制线程的数量。这能够节省时间,由于你不用为每一个任务都固定的去建立线程。注意:在任何线程池中,现有线程在可能的状况下都会复用。CachedThreadPool 在程序执行过程当中一般会建立于所须要数量相同的线程,而后在它回收旧线程时中止建立新的线程,所以它是首选。只有当这种方式引起问题时才须要切换到 FixedThreadPool。
SingleThreadExecutor 就像是线程数量为 1 的 FixedThreadPool。若是向其中提交了多个任务,那么这些任务将排队,每一个任务都会在下一个任务开始以前结束,全部的任务将使用相同的线程。
下面的示例你会看到每一个任务都是按照它提交的顺序在下一个任务开始以前完成的。
public class CachedThreadPool {
public static void main(String[] args) {
// TODO Auto-generated method stub
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 2; i++) {
executorService.execute(new LiftOff());
}
executorService.shutdown();
}
}
复制代码
执行结果:
#0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(Liftoff!), #1(9), #1(8), #1(7), #1(6), #1(5), #1(4), #1(3), #1(2), #1(1), #1(Liftoff!),
复制代码
假如你有大量的任务将使用文件系统。你能够运用 SingleThreadExecutor 来运行这些线程,以确保任意时刻在任何线程中都只要惟一的任务在运行。这种方式你不须要再共享资源上同步。
Runnable 是执行工做的独立任务,可是他不反回任何值。若是你但愿在任务执行完成时可以返回值,那么能够实现 Callable 接口。它是具备类型参数的泛型,它的类型参数表示的是从方法 call() 中返回的值,而且必须使用 ExecutorService.submit() 方法调用它,看示例代码:
public class TaskWithResult implements Callable<String>{
private int id;
protected TaskWithResult(int id) {
super();
this.id = id;
}
@Override
public String call() throws Exception {
// TODO Auto-generated method stub
return "任务执行完毕"+id;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
ExecutorService executorService = Executors.newCachedThreadPool();
ArrayList<Future<String>> result = new ArrayList<>();
for (int i = 0; i < 3; i++) {
result.add(executorService.submit(new TaskWithResult(i)));
}
for (Future<String> future : result) {
try {
System.out.println(future.get());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
executorService.shutdown();
}
}
}
}
复制代码
执行结果:
任务执行完毕0
任务执行完毕1
任务执行完毕2
复制代码
submit 方法会产生 Future 对象,它用 Callable 返回结果的特定类型进行了参数化。可使用 isDone() 方法查询 Future 对象是否完成。当任务完成时能够调用 get() 方法获取结果。
影响任务行为的一种简单方法是调用 sleep(),这将使任务终止执行给定的时间。
public class SleepingTask extends LiftOff {
@Override
public void run() {
// TODO Auto-generated method stub
try {
while(countDown-- > 0) {
System.out.print(status());
// Old-style:
// Thread.sleep(100);
// Java SE5/6-style:
TimeUnit.MILLISECONDS.sleep(100);
}
} catch(InterruptedException e) {
System.err.println("Interrupted");
}
}
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
for(int i = 0; i < 5; i++)
exec.execute(new SleepingTask());
exec.shutdown();
}
}
复制代码
对 sleep() 的调用会抛出异常,而且能够看到,它在 run() 中被捕获。Java SE5 中引入更显示的 sleep() 版本,做为 TimeUnit 类的一部分,这个方法容许你指定 sleep 延迟的时间单元,所以能够提供更好的可阅读性。TimeUnit 还能够被用来执行转换。
线程的优先级将线程的重要性传递给调度器。尽管 CPU 处理线程集的顺序是不肯定的,可是调度器将倾向于让优先权告的线程先执行。然而并不意味着优先级低的线程得不到执行。优先级低的线程仅仅意味着执行的频率较低。
在绝大多数时间全部的线程都应该以默认的优先级运行。视图操纵优先级并不提倡。
下面是一个演示优先级等级的示例,可使用 getPriority() 来读取现有线程的优先级,能够经过 setPriority() 来修改它。
public class SimplePriorities implements Runnable{
private int countDown = 5;
private volatile double d; // No optimization
private int priority;
protected SimplePriorities(int priority) {
super();
this.priority = priority;
}
public String toString() {
return Thread.currentThread() + ": " + countDown;
}
@Override
public void run() {
Thread.currentThread().setPriority(priority);
while(true) {
// An expensive, interruptable operation:
for(int i = 1; i < 100000; i++) {
d += (Math.PI + Math.E) / (double)i;
if(i % 1000 == 0)
Thread.yield();
}
System.out.println(this);
if(--countDown == 0) return;
}
}
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
for(int i = 0; i < 5; i++)
exec.execute(new SimplePriorities(Thread.MIN_PRIORITY));
exec.execute(new SimplePriorities(Thread.MAX_PRIORITY));
exec.shutdown();
}
}
复制代码
执行结果:
Thread[pool-1-thread-6,10,main]: 5
Thread[pool-1-thread-1,1,main]: 5
Thread[pool-1-thread-5,1,main]: 5
Thread[pool-1-thread-2,1,main]: 5
Thread[pool-1-thread-6,10,main]: 4
Thread[pool-1-thread-4,1,main]: 5
Thread[pool-1-thread-3,1,main]: 5
Thread[pool-1-thread-6,10,main]: 3
Thread[pool-1-thread-6,10,main]: 2
Thread[pool-1-thread-1,1,main]: 4
Thread[pool-1-thread-5,1,main]: 4
Thread[pool-1-thread-2,1,main]: 4
Thread[pool-1-thread-4,1,main]: 4
Thread[pool-1-thread-3,1,main]: 4
Thread[pool-1-thread-6,10,main]: 1
Thread[pool-1-thread-5,1,main]: 3
Thread[pool-1-thread-1,1,main]: 3
Thread[pool-1-thread-2,1,main]: 3
Thread[pool-1-thread-4,1,main]: 3
Thread[pool-1-thread-3,1,main]: 3
Thread[pool-1-thread-5,1,main]: 2
Thread[pool-1-thread-4,1,main]: 2
Thread[pool-1-thread-2,1,main]: 2
Thread[pool-1-thread-3,1,main]: 2
Thread[pool-1-thread-1,1,main]: 2
Thread[pool-1-thread-5,1,main]: 1
Thread[pool-1-thread-2,1,main]: 1
Thread[pool-1-thread-4,1,main]: 1
Thread[pool-1-thread-3,1,main]: 1
Thread[pool-1-thread-1,1,main]: 1
复制代码
咱们使用了大量的运算来测试,观察到优先级为 MAX_PRIORITY 的线程被线程调度器优先选择。注意:JDK 有 10 个优先等级,可是与大多数操做系统的映射很差。好比,windows 有 7 个优先级切不固定,因此这种映射关系也是不肯定的。
若是你已经知道你的一次循环迭代过程当中的工做已经完成,就能够给线程调度机制一个暗示:你的工做完成的差很少了,可让别的线程使用 CPU 了。这个暗示将经过调用 yield() 来完成。注意,这只是一种暗示,没有任何机制保证它将会被采纳。当调用 yield() 时,你也是在建议具备相同优先级的其余线程能够运行。
后台线程就是指在程序运行的时候在后台提供一种通用服务的线程,这种线程不是程序必须的一部分。所以,当全部的非后台线程结束时,程序也就终止了,同时户杀死进程中的全部后台进程。反过来讲,只要有任何非后台进程还在运行,程序就不会被终止。好比 main() 就是一个非后台线程。
public class SimpleDaemons implements Runnable {
public void run() {
try {
while(true) {
TimeUnit.MILLISECONDS.sleep(100);
Print.print(Thread.currentThread() + " " + this);
}
} catch(InterruptedException e) {
Print.print("sleep() interrupted");
}
}
public static void main(String[] args) throws Exception {
for(int i = 0; i < 10; i++) {
Thread daemon = new Thread(new SimpleDaemons());
//好比在调用以前设置为后台线程才会生效
daemon.setDaemon(true); // Must call before start()
daemon.start();
}
Print.print("All daemons started");
TimeUnit.MILLISECONDS.sleep(175);
}
}
复制代码
执行结果:
All daemons started
Thread[Thread-8,5,main] concurrency.SimpleDaemons@790d3283
Thread[Thread-5,5,main] concurrency.SimpleDaemons@7e00f258
Thread[Thread-4,5,main] concurrency.SimpleDaemons@ca4a1b4
Thread[Thread-0,5,main] concurrency.SimpleDaemons@fcc3aac
Thread[Thread-9,5,main] concurrency.SimpleDaemons@1c4b4746
Thread[Thread-6,5,main] concurrency.SimpleDaemons@27280786
Thread[Thread-7,5,main] concurrency.SimpleDaemons@11af0a8d
Thread[Thread-2,5,main] concurrency.SimpleDaemons@3b1c2f38
Thread[Thread-3,5,main] concurrency.SimpleDaemons@67deccdf
Thread[Thread-1,5,main] concurrency.SimpleDaemons@12d687a2
复制代码
必须在线程启动以前调用 setDaemon() 方法,才能把他设置为后台线程。
能够经过 isDaemon() 方法来肯定线程是不是一个后台线程。若是是一个后台线程,那么它建立的任何线程都将被自动设置为后台线程。示例:
public class Daemon implements Runnable {
private Thread[] t = new Thread[10];
public void run() {
for(int i = 0; i < t.length; i++) {
t[i] = new Thread(new DaemonSpawn());
t[i].start();
Print.printnb("DaemonSpawn " + i + " started, ");
}
for(int i = 0; i < t.length; i++)
Print.printnb("t[" + i + "].isDaemon() = " +
t[i].isDaemon() + ", ");
while(true)
Thread.yield();
}
}
class DaemonSpawn implements Runnable {
public void run() {
while(true)
Thread.yield();
}
}
复制代码
测试类:
public class Daemons {
public static void main(String[] args) {
Thread d = new Thread(new Daemon());
d.setDaemon(true);
d.start();
Print.printnb("d.isDaemon() = " + d.isDaemon() + ", ");
// Allow the daemon threads to
// finish their startup processes:
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
复制代码
执行结果:
DaemonSpawn 0 started, d.isDaemon() = true, DaemonSpawn 1 started, DaemonSpawn 2 started, DaemonSpawn 3 started, DaemonSpawn 4 started, DaemonSpawn 5 started, DaemonSpawn 6 started, DaemonSpawn 7 started, DaemonSpawn 8 started, DaemonSpawn 9 started, t[0].isDaemon() = true, t[1].isDaemon() = true, t[2].isDaemon() = true, t[3].isDaemon() = true, t[4].isDaemon() = true, t[5].isDaemon() = true, t[6].isDaemon() = true, t[7].isDaemon() = true, t[8].isDaemon() = true, t[9].isDaemon() = true,
复制代码
Daemon 被设置为了后台线程,而后派生出不少子线程,这些线程并无被显式的设置为后台模式,不过他们倒是后台线程。
下面的例子,后台线程在不执行 finally 的时候就会终止其 run() 方法:
class ADaemon implements Runnable {
public void run() {
try {
print("Starting ADaemon");
TimeUnit.SECONDS.sleep(1);
} catch(InterruptedException e) {
print("Exiting via InterruptedException");
} finally {
print("This should always run?");
}
}
}
public class DaemonsDontRunFinally {
public static void main(String[] args) throws Exception {
Thread t = new Thread(new ADaemon());
t.setDaemon(true);
t.start();
}
}
复制代码
上面的程序你将看到 finally 子句不会执行,可是若是注释掉 setDaemon() 方法,就会看到 finally 子句被执行。这是由于当最后一个非后台线程终止时,后台线程会忽然终止。所以一旦 main() 方法退出,JVM 就会当即关闭全部的后台线程。
上面的例子咱们都是直接实现 Runnable 接口。咱们也能够直接从 Thread 继承这种可替换的方式,示例:
public class SimpleThread extends Thread{
private int countDown = 5;
private static int threadCount = 0;
protected SimpleThread() {
super(Integer.toString(++threadCount));
start();
}
@Override
public String toString() {
// TODO Auto-generated method stub
return"#" + getName()+"(" +countDown + ")";
}
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
System.out.println(this);
if (--countDown ==0) {
return;
}
}
}
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
new SimpleThread();
}
}
}
复制代码
执行结果:
#2(5)
#1(5)
#2(4)
#1(4)
#2(3)
#1(3)
#2(2)
#1(2)
#1(1)
#2(1)
复制代码
另一种经常使用的方法是自管理的 Runnable:
public class SelfManaged implements Runnable {
private int countDown = 5;
private Thread t = new Thread(this);
public SelfManaged() { t.start(); }
public String toString() {
return Thread.currentThread().getName() +
"(" + countDown + "), ";
}
public void run() {
while(true) {
System.out.print(this);
if(--countDown == 0)
return;
}
}
public static void main(String[] args) {
for(int i = 0; i < 2; i++)
new SelfManaged();
}
}
复制代码
执行结果:
Thread-1(5), Thread-0(5), Thread-1(4), Thread-0(4), Thread-1(3), Thread-0(3), Thread-1(2), Thread-0(2), Thread-1(1), Thread-0(1),
复制代码
注意:start() 是在构造器中被调用的。可是应该意识到,在构造器中启动线程可能会变得有问题,由于另外一个任务可能在构造器结束以前开始执行,这意味着该任务可以访问处于不稳定状态的对象。这也是咱们优先选择 Executor 而不是显示的建立 Thread 的缘由。
有时经过使用内部类将线程的代码隐藏在类中将会颇有用:
class InnerThread1 {
private int countDown = 5;
private Inner inner;
private class Inner extends Thread {
Inner(String name) {
super(name);
start();
}
public void run() {
try {
while(true) {
print(this);
if(--countDown == 0) return;
sleep(10);
}
} catch(InterruptedException e) {
print("interrupted");
}
}
public String toString() {
return getName() + ": " + countDown;
}
}
public InnerThread1(String name) {
inner = new Inner(name);
}
}
// Using an anonymous inner class:
class InnerThread2 {
private int countDown = 5;
private Thread t;
public InnerThread2(String name) {
t = new Thread(name) {
public void run() {
try {
while(true) {
print(this);
if(--countDown == 0) return;
sleep(10);
}
} catch(InterruptedException e) {
print("sleep() interrupted");
}
}
public String toString() {
return getName() + ": " + countDown;
}
};
t.start();
}
}
// Using a named Runnable implementation:
class InnerRunnable1 {
private int countDown = 5;
private Inner inner;
private class Inner implements Runnable {
Thread t;
Inner(String name) {
t = new Thread(this, name);
t.start();
}
public void run() {
try {
while(true) {
print(this);
if(--countDown == 0) return;
TimeUnit.MILLISECONDS.sleep(10);
}
} catch(InterruptedException e) {
print("sleep() interrupted");
}
}
public String toString() {
return t.getName() + ": " + countDown;
}
}
public InnerRunnable1(String name) {
inner = new Inner(name);
}
}
// Using an anonymous Runnable implementation:
class InnerRunnable2 {
private int countDown = 5;
private Thread t;
public InnerRunnable2(String name) {
t = new Thread(new Runnable() {
public void run() {
try {
while(true) {
print(this);
if(--countDown == 0) return;
TimeUnit.MILLISECONDS.sleep(10);
}
} catch(InterruptedException e) {
print("sleep() interrupted");
}
}
public String toString() {
return Thread.currentThread().getName() +
": " + countDown;
}
}, name);
t.start();
}
}
// A separate method to run some code as a task:
class ThreadMethod {
private int countDown = 5;
private Thread t;
private String name;
public ThreadMethod(String name) { this.name = name; }
public void runTask() {
if(t == null) {
t = new Thread(name) {
public void run() {
try {
while(true) {
print(this);
if(--countDown == 0) return;
sleep(10);
}
} catch(InterruptedException e) {
print("sleep() interrupted");
}
}
public String toString() {
return getName() + ": " + countDown;
}
};
t.start();
}
}
}
public class ThreadVariations {
public static void main(String[] args) {
new InnerThread1("InnerThread1");
new InnerThread2("InnerThread2");
new InnerRunnable1("InnerRunnable1");
new InnerRunnable2("InnerRunnable2");
new ThreadMethod("ThreadMethod").runTask();
}
}
复制代码
若是内部类具备你在其余的方法中须要访问的特殊能力,那这么作将会颇有意义。可是,在大多数时候,建立线程的缘由只是为了使用 Thread 的能力,所以没必要建立匿名内部类。
一个线程能够在其余线程之上调用 join() 方法,其效果是等待一段时间直到第二个线程结束才继续执行。若是某个线程在另外一个线程 t 上调用 join() 方法,此线程将会被挂起,直到目标线程 t 结束才恢复。也能够在调用 join() 时带上一个超时参数,这样若是目标线程在这段时期没有完成结束,join() 方法总能返回。对 join() 方法的调用能够被中断,作法是在调用线程上调用 interrupt() 方法。
下面的例子演示了全部的操做:
线程一:
public 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) {
Print.print(getName() + " was interrupted. " +
"isInterrupted(): " + isInterrupted());
return;
}
Print.print(getName() + " has awakened");
}
}
复制代码
线程二:
public 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();
} catch(InterruptedException e) {
Print.print("Interrupted");
}
Print.print(getName() + " join completed");
}
}
复制代码
测试类:
public class Joining {
public static void main(String[] args) {
Sleeper
sleepy = new Sleeper("Sleepy", 1500),
grumpy = new Sleeper("Grumpy", 1500);
Joiner
dopey = new Joiner("Dopey", sleepy),
doc = new Joiner("Doc", grumpy);
grumpy.interrupt();
}
}
复制代码
执行结果:
Grumpy was interrupted. isInterrupted(): false
Doc join completed
Sleepy has awakened
Dopey join completed
复制代码
执行结果是这样的,先输出了前两句,当 Doc 被执行时,此时 Sleeper 里边的 hoin() 方法被挂起。休眠时间结束调用了 interrupt() 结束挂起以后线程又开始执行。
因为线程的本质特征,使得你不能捕获从线程中逃逸的异常。一旦异常逃出任务的 run() 方法,它就会向外传播到控制台,除非你采起特殊的步骤捕获这种错误的异常。在 Java SE5 以后,能够用 Executor 来解决这个问题。
下面的任务老是会抛出一个异常,该异常会传播到其 run() 方法的外部:
public class ExceptionThread implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
throw new RuntimeException();
}
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new ExceptionThread());
}
}
复制代码
执行结果:
Exception in thread "pool-1-thread-1" java.lang.RuntimeException
at concurrency.ExceptionThread.run(ExceptionThread.java:11)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
复制代码
咱们把调用语句加入到 try-catch 语句块中:
public static void main(String[] args) {
try {
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new ExceptionThread());
} catch (Exception e) {
// TODO Auto-generated catch block
//e.printStackTrace();
}
}
复制代码
执行结果:
Exception in thread "pool-1-thread-1" java.lang.RuntimeException
at concurrency.ExceptionThread.run(ExceptionThread.java:11)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
复制代码
产生于前面相同的结果:未捕获异常。
为了解决这个问题,咱们须要修改 Executor 产生线程的方式。Thread.UncaughtExceptionHandler 是 Java SE5 中的新的接口,它容许你在每一个 Thread 对象上都附着一个异常处理器。它的 uncaughtException() 会在线程因未捕获异常面临死亡时调用。
咱们建立一个 ThreadFactory 它将在每个新建立的 Thread 对象上附着一个 Thread.UncaughtExceptionHandler。
首先自定义一个异常捕获器:
public class MyUncaughtExceptionHandler implements UncaughtExceptionHandler{
@Override
public void uncaughtException(Thread t, Throwable e) {
// TODO Auto-generated method stub
System.out.println("自定义的异常捕获"+e);
}
}
复制代码
实现一个线程工厂,将产生的线程加入异常捕获:
public class HandlerThreadFactory implements ThreadFactory{
@Override
public Thread newThread(Runnable r) {
System.out.println("建立一个新的线程");
Thread thread = new Thread(r);
thread.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
System.out.println("添加异常捕获结束");
return thread;
}
}
复制代码
测试代码:
public class CaptureUncaughtException {
public static void main(String[] args) {
//添加进到构造方法
ExecutorService executorService = Executors.newCachedThreadPool(new HandlerThreadFactory());
executorService.execute(new ExceptionThread());
}
}
复制代码
执行结果:
建立一个新的线程
添加异常捕获结束
建立一个新的线程
添加异常捕获结束
自定义的异常捕获java.lang.RuntimeException
复制代码
如今看到了未捕获的异常是经过 uncaughtException 来捕获的。
若是你要在代码中使用相同的异常处理器,那么更简单的方法是在 Thread 类中设置一个静态域,并将这个处理器设置为默认的异常捕获处理器:
public class SettingDefaultHandler {
public static void main(String[] args) {
Thread.setDefaultUncaughtExceptionHandler(
new MyUncaughtExceptionHandler());
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new ExceptionThread());
}
}
复制代码
注意:默认的异常处理器只有在线程未设置专有的异常处理器状况下才会被调用。 关注个人公号能够获取更多的内容: