线程是操作系统的最小执行单元,每个线程都归属于一个进程,一个进程往往是一个独立的服务。
创建线程
创建线程的过程分两步:声明任务和启动线程。声明任务是定义线程需要执行的具体内容,启动线程是将任务托管到线程上交给操作系统去执行。
声明任务
声明任务的方式有三种,分别是:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
下面分别看一下这三种方式的使用:
- 继承Thread类
public static class ThreadTask extends Thread {
//覆盖run方法,在此处编写业务逻辑
@Override
public void run() {
System.out.println("Hello, I'am ThreadTask");
}
}
public static void main(String[] args) {
//创建线程实例
Thread thread = new ThreadTask();
//启动线程
thread.start();
}
这种方式虽然能够实现预期效果,但并不推荐通过继承Thread来定义一个线程,因为这样会把任务到语义耦合到线程上。
- 实现Runnable接口
public static class RunnableTask implements Runnable {
//覆盖run方法,在此处编写业务逻辑
@Override
public void run() {
System.out.println("Hello, I'am RunnableTask");
}
}
public static void main(String[] args) {
//声明任务
Runnable task = new RunnableTask();
//声明线程
Thread thread = new Thread(task);
thread.start();
}
与继承Thread相比,这种方式可以将要执行到业务逻辑和线程解耦合,线程只描述一个具体到执行单元,Runnable定义任务到具体内容。
- 实现Callable接口
Callable接口与Runnable接口具有相同的功能,区别在于Callable接口定义的任务可以获取其返回值,同时,提交Callable任务需要通过Java 5提高的线程调度框架ExecutorService,而不是用Thread.start()来启动线程。
public static class CallableTask implements Callable<String> {
@Override
public String call() throws Exception {
return "Hello, I'am CallableTask";
}
}
public static void main(String[] args) throws Exception{
//声明一个线程池
ExecutorService executorService = Executors.newCachedThreadPool();
//创建Callabletask实例
Callable<String> callableTask = new CallableTask();
//提交任务并等待返回值
Future<String> result = executorService.submit(callableTask);
//打印返回值内容
System.out.println(result.get());
}
终止线程
终止线程的前提是线程一直在运行,那么怎么安全的通知线程终止运行呢,首先想到的是判断flag标记
public static class RunnableTask1 implements Runnable {
@Getter
@Setter
//volatile 保证变量的内存可见性,禁用指令重排序
private volatile boolean canceled = false;
@Override
public void run() {
try {
while (!canceled) {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + " is running");
}
System.out.println(Thread.currentThread().getName() + " stoped, flag:" + canceled);
}catch (Exception ex) {
}
}
}
public static void main(String[] args) throws Exception {
RunnableTask1 runnable = new RunnableTask1();
Thread t = new Thread(runnable);
t.start();
Thread t2 = new Thread(runnable);
t2.start();
TimeUnit.SECONDS.sleep(3);
runnable.canceled = true;
}
result:
Thread-1 is running
Thread-0 is running
Thread-0 is running
Thread-1 is running
Thread-0 is running
Thread-1 is running
Thread-0 stoped, flag:true
Thread-1 stoped, flag:true
Java给开发者提供来一种jvm层面的,更安全的线程interrupt策略,几乎所有的线程中断方式最终都依赖于线程中断,那么就来看一下Java中都线程中断。
线程中断
将到线程中断,就不得不看一下与线程中断机密关联到三个方法
new Thread().interrupted()
Thread.interrupted()
new Thread().isInterrupted()
线程中断就相当于上面提到到一个标记,只不过这个标记是��️Java提供的,下面通过一个例子来了解一下上面三个方法的作用和区别
private static class InterruptedTask implements Runnable {
public volatile boolean canceled = false;
@Override
public void run() {
try{
while (!canceled) {
//该方法会抛出InterruptedException
//TimeUnit.SECONDS.sleep(1);
System.out.println("Task is running...");
}
//System.out.println("Task is end");
}catch (Exception ex) {
ex.printStackTrace();
//System.out.println("ex flag:" + Thread.interrupted());
}
}
}
public static void main(String[] args) throws Exception{
InterruptedTask task = new InterruptedTask();
Thread thread = new Thread(task);
thread.start();
System.out.println("main flag1:" + thread.isInterrupted()); //false
TimeUnit.MILLISECONDS.sleep(1);
//设置中断状态
thread.interrupt();
task.canceled = true;
System.out.println("main flag2:" + thread.isInterrupted()); //true
}
从上面的例子中可知:
- new Thread().isInterrupted() 方法用于获取当前线程的中断状态
- new Thread().interrupted() 方法用于设置当前线程的中断状态,即中断当前线程
下面有意思的来了,如果将
while (!canceled) {
//该方法会抛出InterruptedException
//TimeUnit.SECONDS.sleep(1);
System.out.println("Task is running...");
}
中的TimeUnit.SECONDS.sleep(1);
放开,那么得到的结果就是
main flag2:false
main flag2:false
这是因为,当线程中抛出Interrupted异常时,jvm会自动重置中断状态为false,至于为什么这么做,我想是为了提供更加安全的现在中断策略。
下面来演示一下Thread.interrupted()。
private static class InterruptedTask implements Runnable {
//public volatile boolean canceled = false;
@Override
public void run() {
try{
while (!Thread.interrupted()) {
//该方法会抛出InterruptedException
TimeUnit.SECONDS.sleep(1);
System.out.println("Task is running...");
}
System.out.println("Task is end");
//throw new InterruptedException();
}catch (Exception ex) {
ex.printStackTrace();
//System.out.println("ex flag:" + Thread.interrupted());
}
}
}
public static void main(String[] args) throws Exception{
InterruptedTask task = new InterruptedTask();
Thread thread = new Thread(task);
thread.start();
System.out.println("main flag1:" + thread.isInterrupted()); //false
TimeUnit.MILLISECONDS.sleep(1);
//设置中断状态
thread.interrupt();
//task.canceled = true;
System.out.println("main flag2:" + thread.isInterrupted()); //false
}
此时的输出结果为:
main flag1:false
main flag2:false
因为线程最终终止运行了,就说明Thread.interrupted()在某个时刻的返回值是ture,那么为什么最后获取中断状态时是false呢? 来看一下该方法的说明:
Tests whether the current thread has been interrupted. The
* <i>interrupted status</i> of the thread is cleared by this method. In
* other words, if this method were to be called twice in succession, the
* second call would return false (unless the current thread were
* interrupted again, after the first call had cleared its interrupted
* status and before the second call had examined it).
其中有一句话是:
The interrupted status of the thread is cleared by this method
也就是调用该方法被调用时,会清除线程的中断状态,即使在该线程上调用了interrupt()方法,线程最终的中断状态还是false,因为被interrupt()方法重置了。
总结一下:
- interrupt()方法用于设置现在中断状态为true
- isInterrupted()方法用于获取线程线程的中断状态
- Thread.interrupted()用于获取当前线程的中断状态,同时还会重置中断状态
- 当线程中抛出InterruptedException时,线程的中断状态会被jvm