一、简介
在计算机的世界里,当我们谈论并发时,我们指的是一系列的任务同时运行于一个计算机中。这里说的同时运行,在计算机拥有多于一个处理器或者是一个多核处理器的时候才是真正的同时,在计算机只拥有单核处理器的时候,它指的是表面上的同时运行。
所有的现代操作系统都允许并发任务的执行。在听歌和阅读网页上新闻的同时,你还能阅读电子邮件。我们可以说这种类型的并发是进程级别的并发。但在一个进程内部,我们也可以拥有多个同时运行的任务。那些运行在一个进程中的并发任务被称作线程。
与并发相关的另一个概念是并行。它与并发之间存在着紧密的联系和区别。有人认为,在单核处理器中执行具有多个线程的进程就叫做并发,这个并发被认为是表面的并发。同时,在多核处理器或者多处理中执行具有多个线程的进程就叫做并行。其他人则认为,当一个进程的多个线程执行之间没有预定义好的顺序时就叫做并发,当使用多个线程去简化一个问题的求解,同时所有的线程按照一定的顺序执行时就叫做并行。
本章呈现了一系列的代码秘诀,这些代码秘诀演示了如何使用 Java 7 API 去执行基本的线程操作。你可以从中学到在Java程序中如何创建和运行线程,如何控制线程的执行,如何分组线程并使用线程组的方式来操纵组内线程。
二、创建和运行一个线程
在这个秘诀中,我们将学到如何在Java程序中去创建和运行一个线程。就像Java语言中的其他元素一样,线程也是以对象的形式存在。在Java中,创建线程有两种方式:
(A) 继承 Thread 类,覆写 run() 方法
(B) 创建一个新类,该类实现 Runnable 接口,该类的对象传给 Thread 类的对象
在本秘诀中,我们通过一个创建和运行10个线程的简单程序来演示第二种方式.每个线程都是计算和打印输出1到10之间的乘法表。
public class Calculator implements Runnable{ private int number; public Calculator(int number) { this.number = number; } @Override public void run() { for (int i = 1; i <= 10; i++) { System.out.printf("%s: %d * %d = %d\n", Thread.currentThread().getName(), number, i, i*number); } } } public class Main { public static void main(String[] args) { for (int i = 1; i <= 10; i++) { Calculator calculator = new Calculator(i); Thread thread = new Thread(calculator); thread.start(); } } }
main()方法所在的执行线程是由JVM启动执行时创建的,一般我们称之为主线程。
通过调用一个 Thread 对象的 start() 方法,我们创建了一个另外的执行线程。
只有当程序的所有非后台线程结束后,该程序才算结束。但是要注意:一旦某一个线程执行了System.exit()指令,所有的线程都将终止执行,该程序也就结束了。
创建一个 Thread 类的对象并没有创建一个新的执行线程。调用实现了 Runnable 接口的对象的 run() 方法也不会创建一个新线程。只有调用 Thread 对象的 start() 方法才创建新线程。
三、获取和设置线程信息
Thread 类保存有一些能帮助我们识别一个线程的信息属性。通过这些属性,我们可以知道它的状态,控制它的优先级。这些属性是:
(A) ID:每个线程的唯一标识符
(B) Name:线程的名字
(C) Priority:线程的优先级,1到10之间,1最低,不建议修改和利用此属性
(D) Status:线程的状态,六种可能:新建、可运行、被阻塞、等待、等待时间、被终结
在本秘诀中,我们创建拥有10个线程的程序,设置10个线程的名字和优先级,然后展示它们的状态知道它们结束,这些线程会计算一个数字的乘法表。
public class Calculator implements Runnable{ private int number; public Calculator(int number) { this.number = number; } @Override public void run() { for (int i = 1; i <= 10; i++) { System.out.printf("%s: %d * %d = %d\n", Thread.currentThread().getName(), number, i, i*number); } } } public class Main { public static void main(String[] args) { Thread threads[] = new Thread[10]; Thread.State status[] = new Thread.State[10]; for (int i = 0; i < 10; i++) { threads[i] = new Thread(new Calculator(i)); if ((i%2) == 0) { threads[i].setPriority(Thread.MAX_PRIORITY); } else { threads[i].setPriority(Thread.MIN_PRIORITY); } threads[i].setName("Thread " +i); } try (FileWriter fw = new FileWriter("log.txt"); PrintWriter pw = new PrintWriter(fw); ) { for (int i = 0; i < 10; i++) { pw.println("Main : Status of Thread " + i + " : " + threads[i].getState()); status[i] = threads[i].getState(); } for (int i = 0; i < 10; i++) { threads[i].start(); } boolean finish = false; while (!finish) { for (int i = 0; i < 10; i++) { if (threads[i].getState() != status[i]) { writeThreadInfo(pw, threads[i], status[i]); status[i] = threads[i].getState(); } } finish = true; for (int i = 0; i < 10; i++) { finish = finish && (threads[i].getState() == State.TERMINATED); } } } catch (IOException e) { e.printStackTrace(); } } private static void writeThreadInfo(PrintWriter pw, Thread thread, State state) { pw.printf("Main : Id %d - %s\n", thread.getId(), thread.getName()); pw.printf("Main : Priority: %d\n", thread.getPriority()); pw.printf("Main : Old State: %s\n", state); pw.printf("Main : New State: %s\n", thread.getState()); pw.printf("Main : *******************************\n"); } }
如果不指定线程名字,JVM会自动给它分配 Thread-XX 格式的线程名。线程的ID和Status是不能修改的。
如果是想在实现了 Runnable 接口的类的对象中访问线程的信息,可以使用 Thread 类的静态方法 currentThread() 获取执行当前代码的 Thread 对象,并以此来访问线程信息。
需要注意的是:setPriority() 方法可能抛出 IllegalArgumentException 溢出。
重要:本系列翻译文档也会在本人的微信公众号(此山是我开)第一时间发布,欢迎大家关注。