并发编程—— 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());
	}

}


 

 

 

 

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。