《Java7并发编程实战手册》学习笔记(一)——线程管理(上)

335 阅读8分钟

此篇博客为个人学习笔记,如有错误欢迎大家指正

本次内容:

  1. 线程的创建和运行
  2. 线程信息的获取和设置
  3. 线程的中断
  4. 线程中断的控制
  5. 线程的休眠和恢复

1.线程的创建和运行

在java程序中,主要有两种方式创建线程

  1. 继承Thread类,并覆盖run()方法。
  2. 创建一个实现了Runnable接口的类,重写run()方法。在run方法中,编写该线程需要完成的任务的相关代码。将这个类作为参数传入带参数的Thread构造函数即可。

范例实现:

这里我们使用第二种方法来创建线程,我们创建一个名为Calculator的类并实现Runnable接口。在Calculator这个类的run方法中,我们将实现对指定数字进行乘法表运算的功能(Thread.currentThread().getName()可以获得当前线程的名称)。最后,我们在main方法中创建这个类,将其作为一个参数传入Thread的构造函数中,执行start()方法。注意,在我们创建了Calculator类的实例后,可以直接调用其run()方法但不会开启一个新的线程。只有调用thread的start()方法后,才会创建一个新的执行线程并运行run方法。具体代码如下:

package thread.part1.code;
public class Calculator implements Runnable {

    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();
        }
    }

    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);
        }
    }
}

在java程序中,当所有的非守护线程都运行完成时,这个java程序才算是结束。负责执行main()方法的线程结束后,并不会影响其他线程,其他线程将会一直运行直到结束。另外,任何一个线程调用了System.exit()方法后,运行的程序将会结束,也就是所有线程都会结束。

2.线程信息的获取和设置

Thread类中有许多我们经常用到的属性:

  • ID:id是线程的唯一标示符,它不允许被修改
  • Name:name是线程的名称,我们可以为线程指定一个名字,否则JVM将会自动为其分配一个名字:Thread-XX,XX为一组数字
  • Priority:Priority保存了线程对象的优先级,范围是从1到10优先级依次升高,我们可以但不推荐去设置、改变线程的优先级
  • Status:Status是线程对象的状态,同样,这个属性也不允许被修改。java中,此属性有六种值分别为:new、runnable、blocked、waiting、time waiting、terminated

范例实现:

在这个范例中,我们将创建运行十个线程,每个线程都会计算一个数字乘法表。当然,我们会为这个十个线程设置名称和优先级,并将其状态也就是status的变化记录到文件中。这一次我们同样使用第二种方法创建、运行线程。代码中有详细的注释就不再这里赘述了

package thread.part1.code;

import java.io.*;

public class Calculator2 implements Runnable {

    private int number;

    public Calculator2(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 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.println("*********************************************");
    }

    public static void main(String[] args) {
        //创建一个容量为10的线程数组用来存放线程
        Thread[] threads = new Thread[10];
        //创建一个容量为10的线程状态数组用来存放线程状态
        Thread.State[] states = new Thread.State[10];
        //创建10个Calculator2,并通过构造方法传入Thread,设置不同的优先级
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(new Calculator2(i));
            if ((i%2) == 0) {
                //设置线程的优先级,1最小,10最大
                threads[i].setPriority(Thread.MAX_PRIORITY);
            }else {
                threads[i].setPriority(Thread.MIN_PRIORITY);
            }
            //设置线程的名字
            threads[i].setName("Thread" + i);
        }

        //创建PrintWriter对象便于将线程状态的变化写入文件中
        try {
            FileOutputStream file = new FileOutputStream("src/thread/part1/log/Calculator2Log.txt");
            PrintWriter pw = new PrintWriter(file);

            //因当前线程还未启动过,所以状态是new
            for(int i = 0;i<10;i++) {
                pw.println("Main : Status of Thread" + i + " : " + threads[i].getState());
                states[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() != states[i]) {
                        writeThreadInfo(pw,threads[i],states[i]);
                        states[i] = threads[i].getState();
                    }
                }
                /*
                * 遍历一次之后,将finish置为true
                * 判断所有线程是否都已经结束
                * 两者相与表示同时成立后值给finish,以此来决定是否结束
                * */
                finish = true;
                for(int i = 0;i<10;i++) {
                    finish = finish && threads[i].getState() == Thread.State.TERMINATED;
                }
            }
            //没有此条语句的话缓冲区内的字符不会被刷到文件中,可能会导致信息未写入文件或写入不全
            pw.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3.线程的中断

java提供了中断机制,这种机制要求线程检查是否有中断请求。检查到有中断请求后,线程可以选择中断或忽略。
Thread类中,有一个布尔类型的属性表示线程是否被中断。当我们调用interrupt方法时表示发起中断,此时这个属性的值为true。我们可以调用isInterrupted()方法和Thread类的静态方法interrupted()来获取此属性的值。两个方法的区别在于:isInterrupted()方法仅能返回属性的值,而Thread类的静态方法interrupted()可以设置此属性为false。

范例分析:

这里我们使用第一种方法创建线程,使一个类继承Thread类并重写run()方法。我们让新的线程从1开始判断数字是否为质数。主线程创建运行线程后,休眠5秒后发起中断,线程检测到中断后响应中断。代码如下:

package thread.part1.code;

public class PrimeGenerator extends Thread {

    @Override
    public void run() {
        //从1开始进行判断
        long number = 1L;
        //设置一个死循环
        while (true) {
            //通过isPrime方法判断number是否是一个质数
            if (isPrime(number)) {
                System.out.printf("Number %d is Prime\n", number);
            }
            //通过isInterrupted方法检测中断,在这里我们检测到中断后就返回
            if (isInterrupted()) {
                System.out.println("The Prime Generator has been Interrupted");
                return;
            }
            number++;
        }
    }

    //判断一个数字是否是质数
    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;
    }

    public static void main(String[] args) {
        Thread task = new PrimeGenerator();
        task.start();
        //运行线程后,主线程休眠5秒后发起中断
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //发起中断
        task.interrupt();
    }
}

4.线程中断的控制

当我们的线程实现了较为复杂的算法或者有递归调用的方法,上一种控制中断的方法就有些力不从心了。我们使用一种新的机制来控制中断,当我们检查到线程中断时,就抛出InterruptedException这个异常。最后在run()方法中捕获这个异常并进行相应的处理。

范例分析:

在这个范例中,我们实现一个文件搜索功能。给定文件夹和带搜索的文件名称,来查询文件的具体路径。具体代码如下:

package thread.part1.code;

import java.io.File;
import java.util.concurrent.TimeUnit;

public class FileSearch implements Runnable {

    //文件夹路径
    private String initPath;
    //待搜索文件的名称
    private String fileName;

    public FileSearch(String initPath, String fileName) {
        this.initPath = initPath;
        this.fileName = fileName;
    }

    @Override
    public void run() {
        //以文件夹路径作为参数创建一个文件
        File file = new File(initPath);
        //如果文件是一个文件夹,执行directoryProcess方法
        if (file.isDirectory()) {
            try {
                directoryProcess(file);
                //捕获异常并进行相应的中断响应
            } catch (InterruptedException e) {
                System.out.printf("%s: The search has been interrupted",Thread.currentThread().getName());
            }
        }
    }

    public void directoryProcess(File file) throws InterruptedException{
        //以数组形式得到文件夹中的所有文件
        File[] files = file.listFiles();
        /*
        * 如果数组不为空,遍历数组
        * 遍历到的文件如果是文件夹,继续执行directoryProcess方法,向下深入
        * 如果是文件,则执行fileProcess方法
        * 通过静态方法interrupted检测是否有中断,有的话抛出异常
        * */
        if (files != null) {
            for (int i = 0; i < files.length; i++) {
                if (files[i].isDirectory()) {
                    directoryProcess(files[i]);
                } else {
                    fileProcess(files[i]);
                }
            }
            if (Thread.interrupted()) {
                throw new InterruptedException();
            }
        }
    }

    public void fileProcess(File file) throws InterruptedException{
        //将文件名和待查询文件名进行对比,如果一样则打印结果
        if (file.getName().equals(fileName)) {
            System.out.println(Thread.currentThread().getName() + " : " + file.getAbsolutePath());
        }
        //检测中断,如果有中断则抛出异常
        if(Thread.interrupted()) {
            throw new InterruptedException();
        }
    }

    public static void main(String[] args) {
        //通过构造函数传参,这里设置文件夹为C盘
        FileSearch fileSearch = new FileSearch("C:/", "netty-bom-4.1.27.Final.pom.part");
        Thread thread = new Thread(fileSearch);
        thread.start();
        try {
            //主线程休眠10秒
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //发起中断
        thread.interrupt();
    }
}

5.线程的休眠和恢复

我们可以通过sleep()方法让线程休眠,休眠期间线程将会释放CPU。sleep()方法有两种使用方式,第一种Thread.sleep(毫秒数)使当前线程休眠;另一种是通过TimeUnit枚举类元素进行调用,具体实例如下

范例分析:

在这个范例中,我们创建并运行一个线程,让其每秒打印一次当前时间,循环十次后结束。代码如下:

package thread.part1.code;

import java.util.Date;
import java.util.concurrent.TimeUnit;

public class FileClock implements Runnable {
    @Override
    public void run() {
        //循环10次
        for (int i = 0; i < 10; i++) {
            System.out.println(new Date().toString());
            try {
                //使当前线程睡眠一秒,效果等同于Thread.sleep(1000)
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return;
    }

    public static void main(String[] args) {
        FileClock fileClock = new FileClock();
        Thread thread = new Thread(fileClock);
        thread.start();
        System.out.println("main方法执行完毕");
    }
}