线程(Thread)相对于进程(Process)更轻,有时被称为轻量进程(Lightweight Process,LWP),是程序执行流的最小单元。线程切换起来更快速,因此现在使用进程作为资源分配的基本单位,将线程作为CPU调度的基本单位。
线程实体 = 程序(Code) + 数据(Data) + 线程控制块(TCB)
线程在的生命周期中有几个状态:创建、就绪、运行、阻塞、终止。
在单个程序中同时运行多个线程完成不同的工作,称为多线程。在Java中创建线程有三种方式:
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 接口的类。然后实现 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是类似于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方法。
(未完待续)
本文标签: Java
暂无评论,赶紧发表一下你的看法吧。