Java知识点复习与总结(四)—— 多线程

Java 阅读: 832

什么是线程

线程(Thread)相对于进程(Process)更轻,有时被称为轻量进程(Lightweight Process,LWP),是程序执行流的最小单元。线程切换起来更快速,因此现在使用进程作为资源分配的基本单位,将线程作为CPU调度的基本单位。

线程实体 = 程序(Code) + 数据(Data) + 线程控制块(TCB)

线程在的生命周期中有几个状态:创建、就绪、运行、阻塞、终止。

创建线程

在单个程序中同时运行多个线程完成不同的工作,称为多线程。在Java中创建线程有三种方式:

继承 Thread 类

java.lang 包提供了 Thread 类,继承 Thread 类并重写 run() 方法就能创建一个线程类。

例子(来自jdk文档):

class PrimeThread extends Thread {
    long minPrime;

    public PrimeThread(long minPrime) {
        this.minPrime = minPrime;
    }

    @Override
    public void run() {
        // compute primes larger than minPrime
    }
}

然后使用下面的代码创建创建一个线程并启动。

PrimeThread p = new PrimeThread(143);
p.start();

每个线程都有一个标识名,多个线程可以同名。如果线程创建时没有指定标识名,就会为其生成一个新名称。

实现Runnable接口

创建线程的另一种方法是实现 Runnable 接口的类。然后实现 run() 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。采用这种风格的同一个例子如下所示(来自jdk文档):

class PrimeRun implements Runnable {
    long minPrime;
    PrimeRun(long minPrime) {
        this.minPrime = minPrime;
    }

    public void run() {
        // compute primes larger than minPrime
    }
}

然后,下列代码会创建并启动一个线程:

PrimeRun p = new PrimeRun(143);
new Thread(p).start();

实际上Thread也是通过实现Runnable接口实现的。Thread类源码如下:

public class Thread implements Runnable {
    ...
}

使用 Callable 和 Future 创建线程

使用以上两种方法的创建线程运行后不能有返回值,但使用Callable 和 Future创建的线程可以有返回值。

Callable是类似于Runnable的接口,源码如下:

public interface Callable<V> {
    V call() throws Exception;
}

例子:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i ++) {
            sum += i;
        }
        return sum;
    }
}

public class TestThread {
    public static void main(String[] args) {

        //使用Callable方式创建线程,需要FutureTask类的支持,用于接收运算结果,可以使用泛型指定返回值的类型
        FutureTask<Integer> task = new FutureTask<Integer>(new MyCallable());

        //启动线程
        new Thread(task).start();

        // 接收运算结果
        // 只有当该线程执行完毕后才会获取到运算结果,等同于闭锁的效果
        try {
            int sum = task.get();
            System.out.println("sum is " + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

线程控制

从上面的创建方法中,可以看到实际上各种方法都使用了Thread类创建线程,因此线程的控制也是通过调用Thread的方法实现的。下面就来介绍一下这些方法。

等待线程结束

等待一个线程结束使用join方法,它有三种形式:

方法 说明
void join() 等待该线程终止
void join(long millis) 等待该线程终止的时间最长为 millis 毫秒
void join(long millis, int nanos) 等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒

三个join方法功能都是一样的,就是等待一个线程终止,调用该方法的进程会进入阻塞状态,直到被等待的线程执行结束后止才会被唤醒。

注意join方法可能会抛出 InterruptedException 异常,因此需要加上try-catch或throws声明。

利用join方法我们可以修改Callable 和 Future中的例子,将接受结果部分修改为:

        // 接收运算结果
        // 只有当该线程执行完毕后才会获取到运算结果,等同于闭锁的效果
        try {
            thread.join();  //等待计算完成
            try {
                int sum = task.get();
                System.out.println("sum is " + sum);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        } catch (InterruptedException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }

暂停线程

使用sleep方法可以让当前正在执行的线程暂停一段时间,并进入阻塞状态。sleep()方法有两种重载形式:

方法 说明
static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响
static void sleep(long millis, int nanos) 在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响

线程让步

yield方法可以暂停当前正在执行的线程对象,并执行其他线程(有点像循环里面的continue语句,只是暂停,一下但并不会结束掉)。

方法 说明
static void yield() 暂停当前正在执行的线程对象,并执行其他线程

守护线程

与守护线程相关的是setDaemon方法:

方法 说明
void setDaemon(boolean on) 将该线程标记为守护线程或用户线程

这里有两个名词:守护线程和用户线程。他们是什么呢?

在Java中,分为两种线程:用户线程和守护线程。

守护线程是指在后台默默守护用户线程的一种线程,它在后台为提供用户线程一些通用服务,比如垃圾回收线程就是一个很称职的守护者。并且守护线程有一个特点:当一个进程中所有的用户线程结束时,守护线程也会自动结束,程序也就终止了。因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行了。

中断线程

与中断线程相关的方法有三个:

方法 说明
boolean isInterrupted() 测试一个线程中断标志是否为true
void interrupt() 将中断标志设置为true,并不会真正中断
static boolean interrupted() 测试当前运行的线程中断标志是否为true

从API可以看出,Thread并没有提供真正的中断方法,而只是在类内部维护了一个标志,那么我们如何才能实现真正的中断呢,Java推荐了线程run方法的一种写法实现中断:

public void run() {
    while (Thread.currentThread().isInterrupted() == false) {
        if (/*任务完成*/) {
            Thread.currentThread().interrupt();
        } else {
            // do something ...
        }
    }
}

意思就是每次完成一个小任务后去检测中断标志,如果中断标志位true则结束循环,退出run方法。

(未完待续)

版权声明:本文为博主原创文章,转载需注明来自: 洛洛の空间