1.线程的状态
2.如何创建,终止线程
线程作为操作系统调度的最小单元,并且能够让多线程同时执行,极大的提高 了程序的性能,在多核环境下的优势更加明显。但是在使用多线程的过程中, 如果对它的特性和原理不够理解的话,很容易造成各种问题
线程的状态
线程一共有 6 种状态(NEW、RUNNABLE、BLOCKED、WAITING、TIME_WAITING、TERMINATED)
状态值 | 描述 | |
---|---|---|
NEW | 初始状态,线程被构建,但是还没有调用 start 方法 | |
RUNNABLE | 运行状态,JAVA 线程把操作系统中的就绪和运行两种状态统一称为“运行中” | |
BLOCKED | 阻塞状态,表示线程进入等待状态,也就是线程因为某种原因放弃了 CPU 使用权,阻塞也分为几种情况 | |
WAITING | 等待状态 | |
TIME_WAITING | 超时等待状态,超时以后自动返回 | |
TERMINATED | 终止状态,表示当前线程执行完毕 |
BLOCKED
等待阻塞:运行的线程执行 wait 方法,jvm 会把当前线程放入到等待队列 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被其他线程锁占 用了,那么 jvm 会当前的线程放入到锁池中 其他阻塞:运行的线程执行 Thread.sleep 或者 t.join 方法,或者发出了 I/O 请求时,JVM 会把当前线程设置为阻塞状态,当 sleep 结束、join 线程终止、 io 处理完毕则线程恢复
通过相应命令显示线程状态 • 打开终端或者命令提示符,键入“jps”,可以获得相应进程的 pid
(JDK1.5 提供的一个显示当前所有 java进程 pid 的命令)
• 根据上一步骤获得的 pid,继续输入 jstack pid
(jstack 是 java 虚拟机自带的一种堆栈跟踪工具。jstack 用于打印出给定的 java 进程 ID 或 core file 或远程 调试服务的 Java 堆栈信息)
线程的创建
1.继承 Thread
/**
* Thread 类本质上是实现了 Runnable 接口的一个实例,代表一个线程的实例。
* 启动线程的唯一方法就是通过 Thread 类的 start()实例方法。start()方法是一个
* native 方法,它会启动一个新线程,并执行 run()方法。这种方式实现多线程很
* 简单,通过自己的类直接 extend Thread,并复写 run()方法,就可以启动新线
* 程并执行自己定义的 run()方法。
*/
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("MyThread.run by extends Thread ");
}
}
2.实现 Runnable接口
/**
* 实现 Runnable 接口创建线程
* 如果自己的类已经 extends 另一个类,就无法直接 extends Thread,此时,
* 可以实现一个 Runnable 接口
*/
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("MyRunnable.run by implements Runnable");
}
}
3.实现 Callable接口
/**
* 实现 Callable 接口通过 FutureTask 包装器来创建 Thread 线程
* <p>
* 有的时候,我们可能需要让一步执行的线程在执行完成以后,
* 提供一个返回值 给到当前的主线程,
* 主线程需要依赖这个值进行后续的逻辑处理,
* 那么这个时候,就需要用到带返回值的线程了。
* Java 中提供了这样的实现方式
*/
public class MyCallableThread implements Callable {
@Override
public Object call() {
System.out.println("MyCallableThread.call ");
return 0;
}
}
调用示例
public class ThreadTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread thread1 = new MyThread();
MyRunnable myRunnable = new MyRunnable();
Thread thread2=new Thread(myRunnable);
thread1.start();
thread2.start();
ExecutorService executorService= Executors.newFixedThreadPool(1);
MyCallableThread callableThread = new MyCallableThread();
Future<Object> future=executorService.submit(callableThread);
System.out.println(future.get());
executorService.shutdown();
}
}
线程的停止
线程的启动过程大家都非常熟悉,但是如何终止一个线程,我相信绝大部分人 在面试的时候被问到这个问题时,也会不知所措,不知道怎么回答。 记住,线程的终止,并不是简单的调用 stop 命令去。虽然 api 仍然可以调用, 但是和其他的线程控制方法如 suspend、resume 一样都是过期了的不建议使 用,就拿 stop 来说,stop 方法在结束一个线程时并不会保证线程的资源正常 释放,因此会导致程序可能出现一些不确定的状态。 要优雅的去中断一个线程,在线程中提供了一个 interrupt 方法
(1)interrupt 方法
当其他线程通过调用当前线程的 interrupt 方法,表示向当前线程打个招呼, 告诉他可以中断线程的执行了,至于什么时候中断,取决于当前线程自己。 线程通过检查资深是否被中断来进行相应,可以通过 isInterrupted()来判断是 否被中断。
通过下面这个例子,来实现了线程终止的逻辑
/**
* 这种通过标识位或者中断操作的方式能够使线程在终止时有机会去清理资源,
* 而不是武断地将线程停止,因此这种终止线程的做法显得更加安全和优雅
*/
private static void interrupt() throws InterruptedException {
Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
i++;
}
System.out.println("Num:" + i);
}, "interruptDemo");
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt();
}
(2)Thread.interrupted
上面的案例中,通过 interrupt,设置了一个标识告诉线程可以终止了,线程中 还提供了静态方法 Thread.interrupted()对设置中断标识的线程复位。比如在 上面的案例中,外面的线程调用 thread.interrupt 来设置中断标识,而在线程 里面,又通过 Thread.interrupted 把线程的标识又进行了复位
/**
* interrupt方法,通过 interrupt,设置了一个标识告诉线程可以终止了,线程中
* 还提供了静态方法 Thread.interrupted()对设置中断标识的线程复位。比如在
* 上面的案例中,外面的线程调用 thread.interrupt 来设置中断标识,而在线程
* 里面,又通过 Thread.interrupted 把线程的标识又进行了复位
*
* @throws InterruptedException
*/
private static void interrupted() throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
i++;
boolean boo = Thread.currentThread().isInterrupted();
if (boo) {
System.out.println("before:" + boo);
//对线程进行复位,中断标识为 false
Thread.interrupted();
System.out.println("after:" + Thread.currentThread().isInterrupted());
}
// System.out.println(i);
// Thread.currentThread().stop();
}
});
thread.start();
TimeUnit.SECONDS.sleep(1);
//设置中断标识,中断标识为 true
thread.interrupt();
}
}
//控制台打印
//before:true
//after:false
(3)InterruptedException
除了通过 Thread.interrupted 方法对线程中断标识进行复位以外,还有一种被 动复位的场景,就是对抛出 InterruptedException 异常的方法,在 InterruptedException 抛出之前,JVM 会先把线程的中断标识位清除,然后才 会抛出 InterruptedException,这个时候如果调用 isInterrupted 方法,将会 返回 false
/**
* 除了通过 Thread.interrupted 方法对线程中断标识进行复位以外,还有一种被
* 动复位的场景,就是对抛出 InterruptedException 异常的方法,在
* InterruptedException 抛出之前,JVM 会先把线程的中断标识位清除,然后才
* 会抛出 InterruptedException,这个时候如果调用 isInterrupted 方法,将会
* 返回 false
*/
private static void throwInterruptedException() throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
System.out.println(123);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
抛出该异常,会将复位标识设置为 false
e.printStackTrace();
}
System.out.println(321);
}
});
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt();//设置复位标识为 true
TimeUnit.SECONDS.sleep(1);
System.out.println(thread.isInterrupted());//false
}
}
有一个疑惑,问线程为什么要复位?
首先我们来看看线程执行 interrupt 以后的源 码是做了什么?
thread.cpp
void Thread::interrupt(Thread * thread) {
trace("interrupt", thread);
debug_only(check_for_dangling_thread_pointer(thread);)
os::interrupt (thread);
}
os_linux.cpp
void os::interrupt(Thread * thread) {
assert (Thread::current () == thread || Threads_lock -> owned_by_self()
, "possibility of dangling Thread pointer");
OSThread * osthread = thread -> osthread();
if (!osthread -> interrupted()) {
osthread -> set_interrupted(true);
// More than one thread can get here with the same value of osthread,
// resulting in multiple notifications. We do, however, want the store
// to interrupted() to be visible to other threads before we execute unpark().
OrderAccess::fence();
ParkEvent * const slp = thread->_SleepEvent ;
if (slp != NULL) slp->unpark() ;
}
// For JSR166. Unpark even if interrupt status already was set
if (thread->is_Java_thread())
((JavaThread*)thread)->parker()->unpark();
ParkEvent * ev = thread -> _ParkEvent;
if (ev != NULL) ev -> unpark();
}
其实就是通过 unpark 去唤醒当前线程,并且设置一个标识位为 true。 并没有 所谓的中断线程的操作,所以实际上,线程复位可以用来实现多个线程之间的 通信。
(4)volatile 修饰的成员变量
除了通过 interrupt 标识为去中断线程以外,我们还可以通过下面这种方式, 定义一个 volatile 修饰的成员变量,来控制线程的终止。这实际上是应用了 volatile 能够实现多线程之间共享变量的可见性这一特点来实现的
这里也只是终止了线程,但线程依然存在
public class VolatileDemo {
private volatile static boolean stop = false;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
int i = 0;
while (!stop) {
i++;
}
});
thread.start();
System.out.println("begin start thread");
Thread.sleep(1000);
stop = true;
}
}