Thinking In Java 21.2 基本的线程进制 学习摘要

  对笔者近期刚阅读完的Thinking In Java的12.2节简单做个概述:

本节的主要内容:

  1. 为什么要引入多线程
  2. 多线程实现的机制:抢占式的轮转调度机制(如何避免抢占可以参考使用协作线程)
  3. 多线程的基本两种实现方式:实现 Runnable 接口、Callable 接口以及继承 Thread 类,并且各自实现的优势
  4. 多线程的安全使用方式:引入了类似线程池的管理机制--Executor(线程执行器),简要介绍了 CachedThreadPool、SingleThreadPool 以及 FixedThreadPool 三种基本的线程执行器
  5. 讲述了 Runnable 接口(无返回值的任务描述)以及 Callable接口(有返回值的任务描述)
  6. Thread 对象的 基本操作方式: Thread t => t.yield() t.start() t.sleep() t.join() t.wait() t.interrupt( join, waitinterrupt笔者没有书写特定的示例,在下方的博客资料中有详细的更优秀的博客做了相关方面的描述)
  7. 如何处理 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;
	}
	
}

附:学习中借鉴的几篇优秀的博客

Java多线程之interrupt()方法与sleep(),join(),wait()的关系

Java多线程编程:Callable、Future和FutureTask浅析(多线程编程之四)

猜你喜欢

转载自my.oschina.net/u/3744313/blog/1820675
今日推荐