创建、运行线程,设置线程属性

Java 9并发编程指南 目录

创建、运行线程,设置线程属性

a中有两种方式创建一个线程。

  • 继承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:线程执行完成。

在本节中,通过范例展现创建和运行10个计算20000以内质数个数的线程。

准备工作

本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。

实现过程

通过如下步骤完成范例:

  1. 创建名为Calculator的类,实现Runnable接口:

    public class Calculator implements  Runnable {
    
  2. 实现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);
        }
    
  3. 然后,实现辅助方法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;
        }
    
  4. 现在实现主类。创建一个包含main()方法的Main类:

    public class Main {
        public static void main(String[] args){
    
  5. 首先,输出线程的最大、最小和默认优先级值:

            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);
    
  6. 然后创建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);
            }
    
  7. 将输出信息写入到文本文件中,所以创建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();
                }
    
  8. 这之后,等待线程结束。在本章“等待线程结束”的课程里,可以使用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();
        }
    }
    

  9. 在上述代码块中,调用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");
    }
    
  10. 运行程序,查看不同的线程如何并行工作的。

工作原理

下面的截图展现程序在控制台输出的部分信息。可以看到所有创建的线程在同时执行它们各自的工作:
pics/01_01.jpg
在截图中,可以看到如何创建线程,以及偶数列线程因为有最高优先级而先执行,其他线程由于有最低优先级而后续执行。下面的截图显示输出的log.txt记录的部分线程状态信息。
pics/01_02.jpg
所有的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异常。

更多关注

  • 本章中“工厂模式创建线程”小节。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值