Java Concurrency - 线程的基础操作
创建线程
在 Java 中,创建线程有两种方式:
- 继承 java.lang.Thread 类,重写 run 方法。
public class MyJob extends Thread { @Override public void run() { System.out.println("Hello Thread"); } public static void main(String[] args) { Thread thread = new MyJob(); thread.start(); } }
- 实现 java.lang.Runnable 接口,然后在创建 Thread 实例时传入 Runnable 参数。
public class MyJob implements Runnable { @Override public void run() { System.out.println("Hello Thread"); } public static void main(String[] args) { Thread thread = new Thread(new MyJob()); thread.start(); } }
获取和设置线程信息
java.lang.Thread 类中的几个信息字段帮助我们来识别一个线程、观察线程的状态或是控制线程的优先级:
- Id: 线程的 ID,每个线程拥有一个唯一的标识。
- Name: 线程的名称。
- Priority: 线程的优先级。优先级的范围是 1~10,值越大优先级越高。
- State: 线程的状态。在 Java 中,线程有六种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING 和 TERMINATED。
上述的属性 java.lang.Thread 类都提供 getter 方法以获取线程的信息,其中 Id 和 State 是不可代码控制修改的,也不提供 setter 方法。
获取当前正在运行的线程
java.lang.Thread 类提供一个静态方法 currentThread(),该方法返回当前正在运行的线程。
public static void main(String[] args) { System.out.println(Thread.currentThread().getName()); // main }
线程休眠
Thread 类的 sleep() 方法接收一个 long 参数 milliseconds,使得当前正在运行的线程休眠 milliseconds 毫秒。
TimeUnit 类同样提供提供 sleep() 方法支持线程休眠。它接收各种时间单位并将其转换成微秒。
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
线程中断
有时候,我们需要在线程任务执行完毕前取消线程任务,例如当下载文件超时时取消下载任务。当 interrupt() 方法被调用时,线程的中断状态被置为 true,但是线程并不会终止。可以利用 isInterrupted 方法检测线程是否中断,并以此对线程进行控制。
public static void main(String[] args) throws Exception { Thread task = new Thread(new Runnable() { public void run() { int num = 2; while (!Thread.currentThread().isInterrupted()) { if (Primes.isPrime(num)) { System.out.println(String.format("Number %d is Prime", num)); } num++; } } }); task.start(); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } task.interrupt(); }
Thread 类还提供一个静态的 interrupted() 方法来判断当前正在运行的线程是否中断。但是 interrupted() 与 isInterrupted() 有些差异:isInterrupted() 方法不会修改中断状态;而 interrupted() 方法会将中断状态置为 true。
有时线程执行的任务十分复杂,使用异常控制会比条件控制更加可取。
/** * This class search for files with a name in a directory */ public class FileSearch implements Runnable { private String initPath; // Initial path for the search private String fileName; // Name of the file we are searching for public FileSearch(String initPath, String fileName) { this.initPath = initPath; this.fileName = fileName; } /** * Main method of the class */ @Override public void run() { File file = new File(initPath); if (file.isDirectory()) { try { directoryProcess(file); } catch (InterruptedException e) { System.out.printf("%s: The search has been interrupted", Thread.currentThread().getName()); cleanResources(); } } } /** * Method for cleaning the resources. In this case, is empty */ private void cleanResources() { } /** * Method that process a directory * * @param file * : Directory to process * @throws InterruptedException * : If the thread is interrupted */ private void directoryProcess(File file) throws InterruptedException { // Get the content of the directory File list[] = file.listFiles(); if (list != null) { for (int i = 0; i < list.length; i++) { if (list[i].isDirectory()) { // If is a directory, process it directoryProcess(list[i]); } else { // If is a file, process it fileProcess(list[i]); } } } // Check the interruption if (Thread.interrupted()) { throw new InterruptedException(); } } /** * Method that process a File * * @param file * : File to process * @throws InterruptedException * : If the thread is interrupted */ private void fileProcess(File file) throws InterruptedException { // Check the name if (file.getName().equals(fileName)) { System.out.printf("%s : %s\n", Thread.currentThread().getName() ,file.getAbsolutePath()); } // Check the interruption if (Thread.interrupted()) { throw new InterruptedException(); } } /** * Main method of the core. Search for the autoexect.bat file * on the Windows root folder and its subfolders during ten seconds * and then, interrupts the Thread * @param args */ public static void main(String[] args) { // Creates the Runnable object and the Thread to run it FileSearch searcher=new FileSearch("C:\\","CHAINLOG.log.2016092210"); Thread thread=new Thread(searcher); // Starts the Thread thread.start(); // Wait for ten seconds try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } // Interrupts the thread thread.interrupt(); } }
join - 等待线程结束
在某些情况下,需要等待某一线程执行完毕后当前的线程才能继续执行,例如等待某个资源初始化完毕后才能执行后续的操作。 Thread 类提供了 join() 方法来实现此功能。当在线程 thread1 中调用 thread2.join() 方法,那么 thread1 线程将会挂起直至线程 thread2 结束。
Example:
public static void main(String[] args) throws Exception { Thread dataSourcesLoader = new Thread(new Runnable() { public void run() { System.out.printf("Begining data sources loading: %s\n",new Date()); try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("Data sources loading has finished: %s\n",new Date()); } }); Thread networkConnectionsLoader = new Thread(new Runnable() { @Override public void run() { System.out.printf("Begining network connections loading: %s\n",new Date()); try { TimeUnit.SECONDS.sleep(6); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("Network connections loading has finished: %s\n",new Date()); } }); dataSourcesLoader.start(); networkConnectionsLoader.start(); dataSourcesLoader.join(); networkConnectionsLoader.join(); System.out.printf("Main: Configuration has been loaded: %s\n",new Date()); }
jion 还有两个重载的方法:join (long milliseconds) 和 join (long milliseconds, long nanos)。第一个方法接收一个 long 参数 milliseconds,如果在线程 thread1 中调用代码 thread2.join(1000),则 thread1 线程挂起直至当 thread2 线程执行完毕;或者当 1000 毫秒过去后,即使 thread2 仍未执行完毕,thread1 依然停止挂起,继续执行。
守护进程
在线程启动前调用 setDaemon(true) 可以将线程设置为守护进程。