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

}
时间: 2024-10-05 21:21:02

并发编程—— Java 内建线程机制【上】的相关文章

Java并发编程 - 逐级深入 看线程的中断

最近有足够的空闲时间 去东看看西看看,突然留意到在Java的并发编程中,有关线程中断的,以前初学时一直没弄清楚的一些小东西. 于是,刚好把收获简单的总结一下,通过此文来总结记录下来. 从源码看线程的状态 在开始分析线程的中断工作之前,我们肯定要先留意一个点,那就是肯定是有开启,才会有与之对应的中断工作出现. 开启一个线程的工作,相信每个Javaer都烂熟于心.它很简单,new一个thread对象,然后调用start方法开启线程. 那么,一个好玩的问题就出现了:既然开启一个线程的步骤如此简单明了,

【转】Java并发编程:如何创建线程?

一.Java中关于应用程序和进程相关的概念 在Java中,一个应用程序对应着一个JVM实例(也有地方称为JVM进程),一般来说名字默认是java.exe或者javaw.exe(windows下可以通过任务管理器查看).Java采用的是单线程编程模型,即在我们自己的程序中如果没有主动创建线程的话,只会创建一个线程,通常称为主线程.但是要注意,虽然只有一个线程来执行任务,不代表JVM中只有一个线程,JVM实例在创建的时候,同时会创建很多其它的线程(比如垃圾收集器线程). 由于Java采用的是单线程编

Java并发编程:进程和线程

.title { text-align: center } .todo { font-family: monospace; color: red } .done { color: green } .tag { background-color: #eee; font-family: monospace; padding: 2px; font-size: 80%; font-weight: normal } .timestamp { color: #bebebe } .timestamp-kwd

并发编程之进程与线程

并发编程之进程与线程 2.1 线程与进程 2.1.1 进程 2.1.2 线程 2.1.3 二者对比 2.2 并行与并发 2.3 应用 2.1 线程与进程 2.1.1 进程 程序指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至CPU,数据加载至内存.在指令运行过程中还需要用到磁盘.网络等设备.进程就是用来加载指令.管理内存.管理IO的. 当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程. 进程就可以视为程序的一个实例.大部分程序可以同时运行多个实例进程(例如记

Java并发编程与技术内幕:线程池深入理解

林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 摘要: 本文主要讲了Java当中的线程池的使用方法.注意事项及其实现源码实现原理,并辅以实例加以说明,对加深Java线程池的理解有很大的帮助. 首先,讲讲什么是线程池?照笔者的简单理解,其实就是一组线程实时处理休眠状态,等待唤醒执行.那么为什么要有线程池这个东西呢?可以从以下几个方面来考虑:其一.减少在创建和销毁线程上所花的时间以及系统资源的开销 .其二.2将当前任务与主线程隔离,能实现和主

Java并发编程的艺术(六)——线程间的通信

多条线程之间有时需要数据交互,下面介绍五种线程间数据交互的方式,他们的使用场景各有不同. 1. volatile.synchronized关键字 PS:关于volatile的详细介绍请移步至:Java并发编程的艺术(三)--volatile 1.1 如何实现通信? 这两种方式都采用了同步机制实现多条线程间的数据通信.与其说是"通信",倒不如说是"共享变量"来的恰当.当一个共享变量被volatile修饰 或 被同步块包裹后,他们的读写操作都会直接操作共享内存,从而各个

Java并发编程——Executor接口及线程池的使用

在如今的程序里,单线程的程序,应该已经比较少了,而Java语言是内置支持多线程并发的,大家都说Java语言内置支持多线程,非常非常的强大和方便,但一直没有深入研究jdk内concurrent包.今天就认真学习了一下java.util.concurrent包,发现jdk多线程编程果然是强大和方便.本文是学习java.util.concurrent包内线程池及相关接口的一些总结. 任务接口抽象 Runnable接口 在java.lang包内,为多线程提供了Runnable接口. public int

[高并发]Java高并发编程系列开山篇--线程实现

ava是最早开始有并发的语言之一,再过去传统多任务的模式下,人们发现很难解决一些更为复杂的问题,这个时候我们就有了并发. 引用 多线程比多任务更加有挑战.多线程是在同一个程序内部并行执行,因此会对相同的内存空间进行并发读写操作.这可能是在单线程程序中从来不会遇到的问题.其中的一些错误也未必会在单CPU机器上出现,因为两个线程从来不会得到真正的并行执行.然而,更现代的计算机伴随着多核CPU的出现,也就意味着不同的线程能被不同的CPU核得到真正意义的并行执行. 那么,要开始Java并发之路,就要开始

java高并发编程(五)线程池

摘自马士兵java并发编程 一.认识Executor.ExecutorService.Callable.Executors /** * 认识Executor */ package yxxy.c_026; import java.util.concurrent.Executor; public class T01_MyExecutor implements Executor { public static void main(String[] args) { new T01_MyExecutor(