首页 > 代码库 > 并发编程—— Java 内建线程机制【上】

并发编程—— Java 内建线程机制【上】

 

         不理解多线程程序设计, 就无法真正理解 JavaSE 和 JDK 源码; 因为线程特性已经与Java 语言紧密地融合在一起。

         如何学习多线程程序设计呢? 那看上去似乎总是显得有些神秘。首先, 必须透彻理解并发程序设计的基本原理和机制, 否则, 只是学习使用那些关键字、类的招式,恐怕只能获得Superficial 的认识, 因为多线程程序设计的难点就在于,在任何情况下都能正确工作, easily writing programs that appear to work but will fail at any time. 必须对线程机制有更深入的认识, 才能知道自己到底在做什么; 其次, 多线程程序也并不神秘, 几个基本要点是: 1. 如何定义、执行和终止任务; 2. 如何让并发任务安全地访问共享资源;3. 如何让任务之间更好地协作和通信; 4. 避免饥饿和死锁。明白这几个要点之后, 就要从语言中寻找对应的机制。

        接下来两篇文章将对 java 内建线程机制做个总结, 其中的代码示例是我自己写的, 有错漏之处, 恳请指出。         

 

1.   术语:
概念层面: 任务: 并发是指多个任务并发。
技术层面: 线程: 线程是多个任务并发执行的实现机制;线程驱动任务执行。 
在单线程环境中,只有一个任务在执行,即从 main方法入口的单一任务实体,通过分配给 main的线程来驱动和执行, 可称之为“主线程”。

 

2.   定义任务:

① 实现 Runnable接口:
public class Xxx implements Runnable {
     public void run() { // codes }
     // codes
}
② 继承 Thread类:
public class Xxx extends Thread {
     public void run() { // codes }
     // codes
尽量使用第一种方式: ① 继承的机会只有一次; ② 避免创建显式的线程对象,而是交给执行器去管理任务的启动、执行和终止。

启动任务: new Thread(Runnable).start().

 

3. 启动、执行和管理任务:使用执行器ExecutorService

Java 提供了一些高层工具, 以便更好地启动、执行和管理任务。 理解底层机制, 使用高层工具; 就好比, 汇编语言有利于理解C语言的内部细节, 但总是应当使用 C 来编程。 任何事物的存在都有其最具价值的场合, 有些东西的存在就是为了支撑更好的东西; 因此, 根据不同的场合选用最合适的工具。

通过以下四种方式之一创建执行器,例如ExecutorService es = Executors.newCachedThreadPool();
Ø        Executors.newFixedThreadPool():创建固定线程数目的线程池,可重用空闲线程;
Ø        Executors.newCachedThreadPool():线程数目不定,可重用空闲线程;
Ø        Executors.newSingleThreadExecutor():单线程执行器,顺序执行任务;
Ø        Executors.newScheduledThreadPool():固定线程数目,支持延迟和周期性任务。
①    执行执行器中的任务:execute(Runnble)和 submit(Callable<T>) 方法
②    关闭执行器不再接受任务: shutdown方法【IsShutdown方法判断执行器是否已经关闭】
③    终止执行器中的任务: shutdownNow方法

④    等待任务执行完毕: awaitTermination(long, TimeUnit)方法 【IsTerminated方法判断执行器中任务是否已经全部执行完毕】

 

下面的例子定义了两个线程, 其中主线程每隔 2 秒钟打印一次信息, 从线程则从控制台接受整数输入并打印出来。 一旦用户输入 0 或输入非法数据, 那么从线程将退出, 进而通过设置结束标记 endflag 来终止主线程. 

 

package threadprogramming.basic;import java.util.Scanner;import java.util.concurrent.TimeUnit;public class IOConcurrency {		private static Scanner s = new Scanner(System.in);		private static volatile boolean endflag = false;		public static boolean end() {		return endflag == true;	}		public static void setflag() {		endflag = true;	}		public static boolean getflag() {		return endflag;	}		public static void main(String[] args)	{		System.out.println("Enter the main thread.");				// 从线程从标准输入中读取整数并输出到控制台		new Thread(new Runnable() {						public void run() {				System.out.println("Enter into thread: " + Thread.currentThread().getName());				try {					while (true) {						int i = s.nextInt();					    System.out.println(Thread.currentThread().getName() + "\tRead: " + i);					    if (i == 0) {					    	System.out.println("Exit from thread: " + Thread.currentThread().getName());					    	break;					    }									     }				}				catch (Exception e) {					System.out.println("Caught: " + e.getMessage());					System.out.println("Exit from thread: " + Thread.currentThread().getName());				}				finally {					IOConcurrency.setflag();				}			}		}).start();				// 主线程每隔 2s 打印一条信息		while (!end()) {			long start = 2000;			try {				TimeUnit.MILLISECONDS.sleep(start);			} catch (InterruptedException e) {				System.out.println("Interrupted in main thread.");			}		    System.out.printf("in main thread: %d ms passed.\t", start);    		    System.out.printf("The End flag: %b\n" ,  getflag());    		}		System.out.println("Exit the main thread.");	}}

 

 

 

 

4.带返回值的任务定义、执行和结果获取
 *①  实现Callable<T>接口,并指定参数类型 T ;
 *②  实现方法: public T call() ,其中 T 是已指定的具体类型。
 *③  创建线程,并使用 ExecutorService.submit(Callable<T> task)来执行;
 *④  创建 Future<T>来存储任务执行对象。

 *⑤  使用Future<T>对象的get()方法获得执行结果。


下面的例子中, 每个线程接受一个整数值 givenNum , 并计算 1 - givenNum 的平方和, 最后返回结果。

package threadprogramming.basic;import java.util.ArrayList;import java.util.List;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;public class ReturnableThread implements Callable<Integer> {	private int givenNum;		public ReturnableThread(int givenNum)	{		this.givenNum = givenNum;	}		/**	 * 计算 1 - givenNum 的平方和	 */	@Override	public Integer call() throws Exception {				int sum = 0;		for (int i=1; i <= givenNum; i++) {			sum += i*i;		}			return sum;	}		public static void main(String[] args)	{		ExecutorService es = Executors.newCachedThreadPool();		List<Future<Integer>> results = new ArrayList<Future<Integer>>();		for (int i=1; i<=10; i++) {			results.add(es.submit(new ReturnableThread(i)));		}		es.shutdown();		for (Future<Integer> result: results) {			try {				System.out.println(result.get());			} catch (InterruptedException e) {				e.printStackTrace();			} catch (ExecutionException e) {				e.printStackTrace();			} 		}			}	}


 

 

 

 

5.任务暂停
调用【Thread.sleep(n), n: 毫秒数】 或 【TimeUnit.MILLISECONDS.sleep(n) , n毫秒数】可使线程休眠一段时间【处于阻塞状态】,在该时间之后线程会自动苏醒。

 如果在线程休眠期间使线程调用interrupt()方法,则会抛出 中断异常 InterruptedException, 该方法可使线程终止阻塞状态【终止任务】。

 下面的例子演示了 sleep 方法的使用, 以及如何中断睡眠线程。 

package threadprogramming.basic;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.TimeUnit;public class SleepInterruption {		public static void main(String[] args)	{		ExecutorService es = Executors.newCachedThreadPool();		for (int i=1; i <= 5; i++) {			es.execute(new Sleeper(i*1000 + 500));		}		try {			TimeUnit.MILLISECONDS.sleep(3000);		} catch (InterruptedException e) {			System.out.println("Interrupted !");		}		es.shutdownNow();	}}class Sleeper implements Runnable {		private int millis;		public Sleeper(int millis) {		this.millis = millis;	}		public void run()	{		long start = 0, end, passed;		try {			start = System.currentTimeMillis();			TimeUnit.MILLISECONDS.sleep(millis);		} catch (InterruptedException e) {			end = System.currentTimeMillis();			passed = end - start;			System.out.printf("Waken up! requiring %6d ms, sleep %6d ms, rest %6d ms\n", millis, passed, millis-passed);		}		System.out.println("time: " + millis);	}	}

 

 

6. 任务调度提示:
调用【Thread.yield()方法】建议线程调度和分派器可以将CPU让给其它线程使用。 注意,只是提示,并不保证一定会进行。对于极其重要的控制和调度,不可依赖此方法;用Thread.yield()方法可以模拟某些看上去是原子操作的执行情况(比如 count++),从而使微小的线程错误更早地浮现。
7.线程异常: 尽量在任务内部处理异常,不要让异常逃逸到更广的范围。
8.后台线程:
Ø        为非后台线程提供通用服务,程序中的可有可无组成成分;
Ø        当所有的非后台线程均终止时(包括 main主线程也退出时),程序将无可阻止地终
止,所有的后台线程都将立即被终止;
Ø        普通线程调用方法 setDaemon(true)即可成为后台线程;
Ø        后台线程创建的线程自动地成为后台线程。
Ø        通过实现线程工厂 ThreadFactory可以用来生成定制属性的线程;将其实现类传给

ExecutorService(ThreadFactory)构造器来执行生成后台线程的工厂。下面的例子演示了后台线程的用法: 通过 DaemonThreadFactory 定制后台线程工厂, 通过传入参数, 使每个后台线程具有一个服务名称并在任务中打印它。 从输出结果可以看出, 当 main 和 计算乘法运算的非后台线程结束后, 所有的后台线程也结束了。

 

package threadprogramming.basic;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.ThreadFactory;import java.util.concurrent.TimeUnit;public class DaemonThreadDemo {		private static String[] services = new String[] {		  "Data Manager", "Network", "Event reporting", "Media devices", "Memory monitor"	};	public static void main(String[] args) {				ExecutorService es = Executors.newCachedThreadPool(new DaemonThreadFactory()); 				// 创建后台线程		for(int i=0; i < 5; i++) {			es.execute(new DaemonServiceThread(services[i]));		}		es.shutdown();				// 创建非后台线程		new Thread(new Runnable() {						@Override			public void run() {					for (long i=1; i < 20000; i++) {					for (long j=1; j < 20000; j++) {						long m = i * j;					}				}			System.out.println("Exit from : " + Thread.currentThread().getName());				}		}).start();				// Main 线程结束		System.out.println("quit main.");	}}// 定制线程工厂class DaemonThreadFactory implements ThreadFactory {	@Override	public Thread newThread(Runnable r) {		Thread t = new Thread(r);		t.setDaemon(true);	    return t;	}}// 后台线程: 间隔地提供服务class DaemonServiceThread implements Runnable {	private String serviceName;		public DaemonServiceThread(String serviceName) {		this.serviceName = serviceName;	}		@Override	public void run() {				while(true) {			try {				TimeUnit.MILLISECONDS.sleep(100);			} catch (InterruptedException e) {				System.out.println("Interrupted.");			}			System.out.println(Thread.currentThread().getName() + " Providing service: " + serviceName + " ... ");		}			}	}


 

 

9.匿名线程:

可以在类的内部、方法内部创建匿名线程(匿名内部类)。可以看到, 匿名内部类在多线程程序设计中,尤其是GUI 程序设计中无处不在。 这里顺带演示了 Thread.join 方法, 该方法在使调用线程挂起, 直到 t.join 的线程 t 执行完毕, 或者产生中断异常。

 

package threadprogramming.basic;public class Joiner extends Thread {		private Thread jThread;		public Joiner(Thread jThread) {		this.jThread = jThread;	}	@Override	public void run() {				 		 try {			    jThread.start();				jThread.join();		 } catch (InterruptedException e) {				System.out.println("Interrupted.");		 }		 System.out.println("Enter thread: " + Thread.currentThread().getName());         for (int i = 0; i < 10; i++)        	 System.out.printf("print [%d]\n", i);		 		 System.out.println("Exit thread: " + Thread.currentThread().getName());	}		public static void main(String[] args) {				System.out.println("Enter thread: " + Thread.currentThread().getName());				Thread joinerThread = new Thread() {						   public void run() {								   System.out.println("Enter thread: " + Thread.currentThread().getName());				   System.out.println("joiner do something...");				   for (int i=0; i < 10; i++) {					   System.out.printf("%d * %d = %d\n", i, i, i*i);				   }				   System.out.println("Exit thread: " + Thread.currentThread().getName());							   }			   		    };		     		new Joiner(joinerThread).start(); 				System.out.println("Exit thread: " + Thread.currentThread().getName());	}}


 

 

 

 

 

并发编程—— Java 内建线程机制【上】