文章最前: 我是Octopus,这个名字来源于我的中文名--章鱼;我热爱编程、热爱算法、热爱开源。所有源码在我的个人github ;这博客是记录我学习的点点滴滴,如果您对 Python、Java、AI、算法有兴趣,可以关注我的动态,一起学习,共同进步。
相关文章:
- 多线程的应用与原理分析1
- 多线程的应用与原理分析2(线程的状态)
- 多线程的应用与原理分析3(原子性、可见性、有序性)
- 多线程的应用与原理分析4(synchronized)
- 多线程的应用与原理分析5(ReentrantLock)
- 多线程的应用与原理分析6(ReentrantLock)
- 多线程的应用与原理分析7(Condition)
- 多线程的应用与原理分析8(countdownlatch)
- 多线程的应用与原理分析9(原子操作)
- 多线程的应用与原理分析10(Semaphore)
- 多线程的应用与原理分析11(线程池)
进程是操作系统进行资源分配的最小单元,线程作为操作系统调度的最小单元,并且能够让多线程同事执行,极大的提高了的性能,在多核环境下的优势更加明显。但是在使用多线程的过程中,如果对它的特性和原理不够理解,很容易造成各种问题。
线程的状态
Java 线程既然能够创建,那么也势必会被销毁,所以线程是存在生命周期的,那么我们接下来从线程的生命周期开始去了解线程。线程一共有 6 种状态(NEW、 RUNNABLE、 BLOCKED、 WAITING、TIME_WAITING、 TERMINATED)
NEW:初始状态,线程被构建,但是还没有调用 start 方法
RUNNABLED:运行状态, JAVA 线程把操作系统中的就绪和运行两种状态统一称为“运行中”
BLOCKED:阻塞状态,表示线程进入等待状态,也就是线程因为某种原因放弃了 CPU 使用权,阻塞也分为几种情况
Ø 等待阻塞:运行的线程执行 wait 方法, jvm 会把当前线程放入到等待队列
Ø 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被其他线程锁占用了,那么 jvm 会把当前的线程放入到锁池中
Ø 其他阻塞:运行的线程执行 Thread.sleep 或者 t.join 方法,或者发出了 I/O请求时, JVM 会把当前线程设置为阻塞状态,当 sleep 结束、 join 线程终止、io 处理完毕则线程恢复
TIME_WAITING:超时等待状态,超时以后自动返回
TERMINATED:终止状态,表示当前线程执行完毕
通过代码演示线程的状态
package lock;
import java.util.concurrent.TimeUnit;
/**
* @author zhangyu
* @version V1.0
* @ClassName: ThreadStatus
* @Description: 测试几个锁的状态
* @date 2019/1/19 18:53
**/
public class ThreadStatus {
public static void main(String[] args) {
// time_waiting
new Thread(() -> {
while (true) {
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "timewaiting").start();
// waiting,线程在ThreadStatus类锁上通过wait进行等待
new Thread(() -> {
while (true) {
synchronized (ThreadStatus.class) {
try {
ThreadStatus.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "waiting").start();
// 线程在ThreadStatus加锁后,不会释放锁
new Thread(new BlockedDemo(), "BlockDemo-01").start();
new Thread(new BlockedDemo(), "BlockDemo-02").start();
}
static class BlockedDemo extends Thread {
public void run() {
while (true) {
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
通过相应命令显示线程状态
打开终端或者命令提示符,键入“jps”,(JDK1.5 提供的一个显示当前所有 java进程 pid 的命令),可以获得相应进程的 pid;
根据上一步骤获得的 pid,继续输入 jstack pid(jstack 是 java 虚拟机自带的一种堆栈跟踪工具。 jstack 用于打印出给定的 java 进程 ID 或 core file 或远程调试服务的 Java 堆栈信息);
线程的停止
线程的启动过程大家都非常熟悉,但是如何终止一个线程,我相信绝大部分人在面试的时候被问到这个问题时,也会不知所措,不知道怎么回答。记住,线程的终止,并不是简单的调用 stop 命令去。虽然 api 仍然可以调用,但是和其他的线程控制方法如 suspend、 resume 一样都是过期了的不建议使用,就拿 stop 来说, stop 方法在结束一个线程时并不会保证线程的资源正常释放,因此会导致程序可能出现一些不确定的状态。要优雅的去中断一个线程,在线程中提供了一个 interrupt 方法
interrupt 方法
当其他线程通过调用当前线程的 interrupt 方法,表示向当前线程打个招呼,告诉他可以中断线程的执行了,至于什么时候中断,取决于当前线程自己。线程通过检查资深是否被中断来进行相应,可以通过 isInterrupted()来判断是否被中断。
package lock;
import java.util.concurrent.TimeUnit;
/**
* @author zhangyu
* @version V1.0
* @ClassName: InterruptDemo3
* @Description: 中断线程
* @date 2019/1/19 18:50
**/
public class InterruptDemo3 {
private static int i;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
i++;
}
System.out.println("Num:" + i);
}, "InterruptDemo3");
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt();
}
}
这种通过标识位或者中断操作的方式能够使线程在终止时有机会去清理资源,而不是武断地将线程停止,因此这种终止线程的做法显得更加安全和优雅;
Thread.interrupted
上面的案例中,通过 interrupt,设置了一个标识告诉线程可以终止了,线程中还提供了静态方法 Thread.interrupted()对设置中断标识的线程复位。比如在上面的案例中,外面的线程调用 thread.interrupt 来设置中断标识,而在线程里面,又通过 Thread.interrupted 把线程的标识又进行了复位;
package lock;
import java.util.concurrent.TimeUnit;
/**
* @author zhangyu
* @version V1.0
* @ClassName: InterruptDemo2
* @Description: 对线程进行复位
* @date 2019/1/19 18:44
**/
public class InterruptDemo2 {
public static void main(String[] args)throws InterruptedException{
Thread thread=new Thread(()->{
while(true){
while (true){
boolean ii=Thread.currentThread().isInterrupted();
if(ii){
System.out.println("before:"+ii);
Thread.interrupted();//对线程进行复位,中断为标识为false
System.out.println("afer:"+Thread.currentThread().isInterrupted());
}
}
}
});
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt();// 设置中断标识,中断标识为true
}
}
其他的线程复位
除了通过 Thread.interrupted 方法对线程中断标识进行复位以外,还有一种被动复位的场景,就是对抛出 InterruptedException 异常的方法,在InterruptedException 抛出之前, JVM 会先把线程的中断标识位清除,然后才会抛出 InterruptedException,这个时候如果调用 isInterrupted 方法,将会返回 false;
package lock;
import java.util.concurrent.TimeUnit;
/**
* @author zhangyu
* @version V1.0
* @ClassName: InterruptDemo
* @date 2019/1/19 18:36
**/
public class InterruptDemo {
public static void main(String[] args)throws InterruptedException{
Thread thread=new Thread(()->{
// while(true){
try{
Thread.sleep(10000);
}catch (InterruptedException e){
e.printStackTrace();
}
// }
});
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt();// 设置复位标识为true
TimeUnit.SECONDS.sleep(1);
System.out.println(thread.isInterrupted());//false
}
}
有同学在问线程为什么要复位?首先我们来看看线程执行 interrupt 以后的源码是做了什么?其实就是通过 unpark 去唤醒当前线程,并且设置一个标识位为 true。 并没有所谓的中断线程的操作,所以实际上,线程复位可以用来实现多个线程之间的通信。
线程的停止方法之 2
除了通过 interrupt 标识为去中断线程以外,我们还可以通过下面这种方式,定义一个 volatile 修饰的成员变量,来控制线程的终止。这实际上是应用了volatile 能够实现多线程之间共享变量的可见性这一特点来实现的。
package lock;
/**
* @author zhangyu
* @version V1.0
* @ClassName: ValotileDemo
* @Description: 优雅的中断线程
* @date 2019/1/19 18:31
**/
public class ValotileDemo {
private volatile static boolean stop = false;
static int i = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (!stop) {
i++;
}
});
thread.start();
System.out.println("begin start thread");
Thread.sleep(1000);
stop = true;
System.out.println(i);
}
}