对笔者近期刚阅读完的Thinking In Java的12.2节简单做个概述:
本节的主要内容:
- 为什么要引入多线程
- 多线程实现的机制:抢占式的轮转调度机制(如何避免抢占可以参考使用协作线程)
- 多线程的基本两种实现方式:实现
Runnable
接口、Callable
接口以及继承Thread
类,并且各自实现的优势 - 多线程的安全使用方式:引入了类似线程池的管理机制--
Executor
(线程执行器),简要介绍了CachedThreadPoo
l、SingleThreadPool
以及FixedThreadPool
三种基本的线程执行器 - 讲述了
Runnable
接口(无返回值的任务描述)以及Callable
接口(有返回值的任务描述) Thread
对象的 基本操作方式:Thread t
=>t.yield()
t.start()
t.sleep()
t.join()
t.wait()
t.interrupt
(join
,wait
和interrupt
笔者没有书写特定的示例,在下方的博客资料中有详细的更优秀的博客做了相关方面的描述)- 如何处理
run()
方法中逃逸的异常(在Main线程中无法try/catch到的)
以下是笔者的笔记,对于Java中的进程的基本概念有很多知识还需要参考操作系统(如:线程的三种基本态:执行态、等待态、执行态以及补充的挂起态和激活态)。大部分笔者自己的理解都放在了代码中,如果有疏漏的还指望dalao们私聊指点下笔者(小萌新一只)!
package cnboy.henu.xb;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
/**
* Thinking In Java 21.2 基本的线程进制 学习的样例代码
* @author Administrator
*
* 最重要要理清概念 “线程”与“任务”,任务需要线程的驱动,Runnable以及Callable接口所做的事情是描述一个任务,而Thread则是开启一个线程,附着在任务上,驱动任务的执行
*/
public class ExcutorTry {
public static void main(String[] args) {
// TODO Auto-generated method stub
// // 执行器对象 提供中间层管理任务对象(线程)
// /**
// * 下面是使用的SingleTheadPool做的示例
// */
// ExecutorService exec1 = Executors.newSingleThreadExecutor();
// // 此对象缓存的线程对象仅只有一个,因此其它需要执行的任务会自动排队等待执行
// for(int i=0;i<10;i++) {
// exec1.execute(new TaskWithoutResult(i));
// }
// exec1.shutdown();
// /**
// * 下面是利用的 CachedThreadPool 做的示例
// */
// ExecutorService exec = Executors.newCachedThreadPool();
// // Future对象正如名字所示,可以代表一个异步任务返回的对象并提供获取其值的方式
// List<Future<String>> results = new ArrayList<Future<String>>();
// for(int i=0;i<10;i++) {
// results.add(exec.submit(new TaskWithResult(i)));
// }
// // 关闭执行器,不让新的任务添加进来
// exec.shutdown();
// for(Future<String> fs:results) {
// try {
// // 判断fs是否已经执行完毕,也可以不加,不加的情况下会自动阻塞等待其执行完毕
// if(fs.isDone()) {
// System.out.println(fs.get());
// }
// }catch(InterruptedException e){
// e.printStackTrace();
// } catch (ExecutionException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// }
// // 这里执行描述的任务的Runner实际上是Main线程而不是launch对象
LiffOff launch = new LiffOff(10);launch.run();
Thread.yield();
// // 这里执行描述的任务对象是线程 t ,此处才是真正的多线程并发执行(main 线程以及 t 线程)
// Thread t = new Thread(new LiffOff(10));
// t.start();System.out.println("Waiting for Liff Out!");
// // 这里模仿了6个线程并发的执行情况(main + 五个new Thread)
// for(int i=0;i<5;i++) {
// new Thread(new LiffOff(10)).start();
// }
/**
* Daemon线程 (守护线程,定义为DaemonThread意味着此线程不属于程序中不可或缺的一部分,常见的 GC线程是Daemon线程)
* Daemon进程fork出来的子线程也会被自动设置为Daemon线程
*/
// // 将线程执行器设置为后台线程执行器(即创建执行任务的线程都是后台线程),采用构造注入的方式将后台线程Factory注入执行器中
// ExecutorService daemonExec = Executors.newCachedThreadPool(new DaemonThreadFactory());
// for(int i =0;i<5;i++) {
// // 后台执行
// daemonExec.execute(new TaskWithoutResult(i));
// }
// daemonExec.shutdown();
// System.out.println("All Daemon Thread Started!");
// try {
// // 等待一下Daemon 线程的执行(有趣的是可以调节等待时间观察Daemon线程执行与非后台线程执行的关系,即非后台线程执行完毕,Daemon线程也会同时退出)
// //TimeUnit.SECONDS.sleep(10);// 后台线程执行完毕
// TimeUnit.MICROSECONDS.sleep(1000);// 后台线程来不及执行完毕
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
/**
* 使用继承Thread类的方式实现多线程,缺点在于无法再继承其他类,而实现Runnable接口任然可以继承其他类
*/
// for(int i=0;i<5;i++) {
// new SimpleThread();
// }
/**
* 通常我们在run()里出现的异常(从线程中逃逸,向父线程传播)如果不加以特殊处理(如使用UncaughtExceptionHandle),而只是简单的在Main里通过try/catch是没办法捕获并处理异常的
* 因此我们需要引入UncaughtExceptionHandle为我们的线程加上一道安全防线(使用在ThreadFactory、Executor)
*
*/
// ExecutorService exceptionExec =
// Executors.newCachedThreadPool(new HandleThreadFactory());
// exceptionExec.execute(new ExceptionThread());
// exceptionExec.shutdown();
}
}
/**
* 不带返回值的任务
* @author Administrator
*
*/
class TaskWithoutResult implements Runnable{
private int id;
public TaskWithoutResult(int id) {
// TODO Auto-generated constructor stub
this.id = id;
System.out.println("Generate Runnable Task : id = "+id);
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(String.format("Task Without Result => id = %d", id));
}
}
/**
* 带返回值的任务 实现的Callable接口
* @author Administrator
*
*/
class TaskWithResult implements Callable<String>{
private int id=0;
public TaskWithResult(int id) {
// TODO Auto-generated constructor stub
this.id = id;
}
/**
* 注意不是Run方法
*/
@Override
public String call() throws Exception {
// TODO Auto-generated method stub
// 模仿下有任务的线程
return String.format("ask With Result => id = %d", id);
}
}
/**
* 这个任务描述是模拟的火箭发射的例子
* @author Administrator
*
*/
class LiffOff implements Runnable{
protected int countDown =10;
private static int taskCount = 0;
private final int id=taskCount++;
public LiffOff(int countDown) {
// TODO Auto-generated constructor stub
this.countDown = countDown;
}
public String Status() {
return String.format("# %d (", id)+(countDown>0?countDown:"Liff Off!")+")";
}
@Override
public void run() {
// 模拟火箭发射倒计时
while(countDown-- >0) {
System.out.println(Status());
// 让步 告诉线程调度器此线程核心工作已完成,可以进行上下文切换了
/**
* 与sleep不同的是,sleep是让线程进入阻塞态,这时无论比线程高优先级还是低优先级的线程都可以被执行
* 而yeild仅仅只是让出此时间片,进入可执行态也就是它并不会等待一次完整的轮转后再之执行,而是有可能随时又进入执行态(可能刚yeild完毕又被调用了),因此它只能让步与它同优先级或者是高优先级的线程
*/
//Thread.yield();
/**
* 换成sleep,可以看到每个线程都会按照时间轮转进行输出,而不是抢占的输出(会按照顺序输出)
*/
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
/**
* 专门用于创建后台进程的ThreadFactory
* @author Administrator
*
*/
class DaemonThreadFactory implements ThreadFactory{
@Override
public Thread newThread(Runnable r) {
// TODO Auto-generated method stub
Thread t = new Thread(r);
// 这里设置线程为后台线程
t.setDaemon(true);
return t;
}
}
/**
* 模拟一种实现线程的方式:继承自Thread类
* @author Administrator
*
*/
class SimpleThread extends Thread{
protected int countDown =5;
private static int taskCount = 0;
public SimpleThread() {
// TODO Auto-generated constructor stub
// 通过调用父类的构造函数,给线程名字赋值
super(Integer.toString(taskCount++));
// 在构造函数中便启动线程 -- 不推荐的方式,仅为了方便做示例
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;
}
}
}
}
/**
* 模拟一个在run()方法中抛出异常的Runnable对象
* @author Administrator
*
*/
class ExceptionThread implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
// 模拟执行过程中抛出异常
Thread t = Thread.currentThread();
System.out.println("run() by "+ t);
System.out.println("eh = "+t.getUncaughtExceptionHandler());
throw new RuntimeException();
}
}
/**
* 模拟一个用以处理逃逸的异常的UncaughtExceptionHandler对象
* @author Administrator
*
*/
class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler{
/**
* uncaughtException方法会在线程因未捕获的异常而面临死亡的时候被调用
*/
@Override
public void uncaughtException(Thread t, Throwable e) {
// TODO Auto-generated method stub
System.out.println("caught :"+e);
}
}
/**
* 模拟的一个为线程设置异常处理的ThreadFactory
* @author Administrator
*
*/
class HandleThreadFactory implements ThreadFactory{
@Override
public Thread newThread(Runnable r) {
// TODO Auto-generated method stub
// 记录创建线程的过程
System.out.println(this+"\tcreating new Thread");
Thread t = new Thread(r);
System.out.println("created \t" + t);
// 给与线程UncaughtException处理器
t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
System.out.println("eh = "+t.getUncaughtExceptionHandler());
return t;
}
}
附:学习中借鉴的几篇优秀的博客