- 继承Thread 类,重写run()方法。
- 创建一个类,实现Runnable接口和run()方法,然后通过Runnable对象作为参数来创建Thread类的一个对象。优先考虑这种方式,灵活性很高。
在本节中,使用第二种方法创建线程。然后学习如何改变线程属性。线程类保存了一些信息属性来帮助我们识别一个线程,知道它的状态,或者控制其优先级。这些属性包括:
- ID:每一个线程的唯一标识符。
- Name:线程的名称。
- Priority:线程对象的优先级。在Java 9中,线程的优先级在1到10之间,1的优先级最低、10的优先级最高。我们不需要改变线程的优先级,这只是作为底层操作系统的一个线索,不保证任何操作。
- Status:线程的状态。在Java中,线程的状态定义在Thread.State枚举类型中,分别是NEW,RUNNABLE,BLOCKED,WAITING,TIME_WAITING,TERMINATED。分别介绍如下:
- NEW:已创建线程,但并未开始运行;
- RUNNABLE:线程已经在JVM上执行;
- BLOCKED:线程阻塞,并且等待监控器分配;
- WAITING:线程等待另一个线程运行完再执行;
- TIMED_WAITING:线程在指定等待时间内等待另一个线程运行完再执行;
- TERMINATED:线程执行完成。
- NEW:已创建线程,但并未开始运行;
在本节中,通过范例展现创建和运行10个计算20000以内质数个数的线程。
准备工作
本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。
实现过程
通过如下步骤完成范例:
-
创建名为Calculator的类,实现Runnable接口:
public class Calculator implements Runnable {
-
实现run()方法,这个方法执行创建的线程指令,计算20000以内的质数个数:
@Override public void run() { long current = 1L; long max = 20000L; long numPrimes = 0L; System.out.printf("Thread '%s' : START\n" , Thread.currentThread().getName()); while (current <= max){ if (isPrime(current)) { numPrimes++; } current ++; } System.out.printf("Thread '%s' : END. Number of Primes : %d\n" , Thread.currentThread().getName(), numPrimes); }
-
然后,实现辅助方法isPrime()。这个方法确定一个数字是否是质数:
private boolean isPrime(long number) { if (number <= 2) { return true; } for (long i = 2; i < number; i++){ if ((number % i) == 0){ return false; } } return true; }
-
现在实现主类。创建一个包含main()方法的Main类:
public class Main { public static void main(String[] args){
-
首先,输出线程的最大、最小和默认优先级值:
System.out.printf("Minimun Priority : %s\n" , Thread.MIN_PRIORITY); System.out.printf("Normal Priority : %s\n" , Thread.NORM_PRIORITY); System.out.printf("Maximum Priority : %s\n" , Thread.MAX_PRIORITY);
-
然后创建10个线程对象执行10个Calculator任务。同时,创建两个队列保存线程对象和它们的当前状态。随后会用这些信息检查这些线程终止。用最高优先级执行五个线程(偶数列),最低优先级执行另外五个线程:
Thread threads[]; Thread.State status[]; threads = new Thread[10]; status = new Thread.State[10]; for(int i = 0 ; i < 10 ; i++){ threads[i] = new Thread(new Calculator()); if((i % 2) == 0) { threads[i].setPriority(Thread.MAX_PRIORITY); }else{ threads[i].setPriority(Thread.MIN_PRIORITY); } threads[i].setName("My Thread " + i); }
-
将输出信息写入到文本文件中,所以创建try-with-resources声明来管理文件。在这段代码块中,在开始线程前将线程的状态记录到文件中,然后,启动线程:
try(FileWriter file = new FileWriter(System.getProperty("user.dir")+"\\log.txt"); PrintWriter pw = new PrintWriter(file)) { 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(); }
-
这之后,等待线程结束。在本章“等待线程结束”的课程里,可以使用join()方法实现这个功能。由于我们需要在线程状态改变时记录下线程信息,所以不适用这个方法。使用如下代码块:
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() == Thread.State.TERMINATED); } } } catch (IOException e) { e.printStackTrace(); } }
-
在上述代码块中,调用writeThreadInfo()方法将线程状态信息记录到文件中。如下是这个方法实现代码:
private static void writeThreadInfo(PrintWriter pw, Thread 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"); }
-
运行程序,查看不同的线程如何并行工作的。
工作原理
下面的截图展现程序在控制台输出的部分信息。可以看到所有创建的线程在同时执行它们各自的工作:
在截图中,可以看到如何创建线程,以及偶数列线程因为有最高优先级而先执行,其他线程由于有最低优先级而后续执行。下面的截图显示输出的log.txt记录的部分线程状态信息。
所有的Java程序都至少包含一个执行线程。当运行程序时,JVM运行程序中调用main()方法执行线程。
当调用Thread对象的start()方法时,将创建另一个执行线程。程序中包含与调用start()方法同样多的执行线程。
Thread类属性存储线程的所有信息。操作系统调度器始终通过线程优先级确定使用处理器的线程,同时根据当前情况实现每个线程的状态。
如果尚未给线程指定名称,JVM按照格式Thread-XX自动命名,其中XX是数字。我们无法修改线程的ID和状态,Thread类未实现setId()和setStatus()方法,因为这些方法在代码中实现修改操作。
当所有线程运行结束时(更具体的,当所有非守护线程运行结束时),Java程序才会终止。如果初始线程(执行main()方法的线程)终止,其他线程将继续执行之道结束。如果其中一个线程使用System.exit()指令来终止程序执行,所有的线程都将终止其各自执行。
在Thread类中创建对象,以及调用实现Runnable接口的类的run()方法都不会创建一个新的执行线程。只有调用start()方法时才会创建一个新的执行线程。
扩展学习
如本节介绍中提及的,还有一种创建执行线程的方法。继承Thread 类,重写run()方法。然后创建对象并调用start()方法获得一个新的执行线程。
通过Thread类的currentThread()方法使用正在运行当前对象的线程。
需要考虑的是,如果用setPriority()方法尝试设置线程优先级不在1-10之间,会抛出IllegalArgumentException异常。
更多关注
- 本章中“工厂模式创建线程”小节。