线程的生命周期
创建:新建一个线程对象,new Thread = new Thread()
就绪:创建了线程对象后,调用了start()方法(注意线程此时只是进入了线程队列,等待获取CPU服务,具备了运行条件,但不代表可以马上运行)
运行:处于就绪状态的线程,一旦获取了CPU服务,便进入了运行状态,开始执行run()方法里面的逻辑
终止:当线程run()方法执行完毕,或者线程调用了stop()方法,那么线程就会终止
阻塞:一个正在执行的线程在某些情况下,由于某种原因暂时让出了CPU资源,暂停了自己的执行,便进入了阻塞状态,如调用了sleep()方法
线程状态。 线程可以处于以下状态之一:
NEW :尚未启动的线程处于此状态。 1
RUNNABLE :在Java虚拟机中执行的线程处于此状态。 2
BLOCKED :被阻塞等待监视器锁定的线程处于此状态。 3
WAITING :正在等待另一个线程执行特定动作的线程处于此状态。 4
TIMED_WAITING :正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。 5
TERMINATED :已退出的线程处于此状态。 6
其中:
New对应的是创建
RUNNABLE对应的是就绪状态&运行状态
BLOCKED、WAITING、TIMED_WAITING对应的是阻塞状态
TERMINATED对应的是终止状态
守护线程
java线程有两类
1、用户线程:运行在前台,执行具体任务
程序的主线程、连接网络的子线程等都是用户线程
(这类线程我们看的见他们在工作,但是摸不着)
2、守护线程:运行在后台,为其他前台线程服务
特点:一旦所有的用户线程都结束运行,那么守护线程会觉得自己没有存在的必要了,就会随着JVM一起停止工作
应用:数据库连接池中的监控线程(如连接池中连接的个数、连接超时的时间等)
…………JVM虚拟机启动后的监控线程(如检测内存使用的情况等),还有最常见的就是:垃圾回收线程!
3、如何设置守护线程
可以通过调用Tread类的setDaemon(true)方法来把当前的线程设置为守护线程
注意事项:
1、setDaemon(true)的这个方法必须在start()方法前调用,不然会抛出illgalThreadstateException异常(首字母应大写,这里是博客的字体,i的大写与l傻傻分不清楚)
2、在守护线程中产生的新线程也是守护线程
3、不是所有的任务都可以分配给守护线程来执行,如读写操作或者计算逻辑(如果守护线程执行读写操作或者计算逻辑,如果用户线程都结束了,那么守护线程也停止了,那么程序执行不完就崩溃了)
代码演示
场景:守护线程向文件中不断的输出字符串,当主线程阻塞停止时,守护线程退出关闭
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Scanner;
class DemonThread implements Runnable{
//用Runnable是为了提高程序的可扩展性
@Override
public void run() {
System.out.println("进入守护线程:"+Thread.currentThread().getName());
try {
writeToFile();
} catch (Exception e) {
e.printStackTrace();
}
//如果守护线程结束,那么就打印退出守护线程
System.out.println("退出守护线程:"+Thread.currentThread().getName());
}
public void writeToFile() throws Exception{//这里是为了方便把所有的异常全给抛出去了
File file = new File("d:"+File.separator+"demo.text");//中间的相当于路径的斜线/
OutputStream os = new FileOutputStream(file,true);
int count = 0;
while(count<999){
os.write(("\r\nword:"+count).getBytes());
System.out.println("守护线程"+Thread.currentThread().getName()
+"向文件中写入了"+count);
count++;
Thread.sleep(1000);
}
}
}
public class DemonThreadDemo {
public static void main(String[] args) {
System.out.println("进入主线程"+Thread.currentThread().getName());
DemonThread dt = new DemonThread();
Thread thread = new Thread(dt);
thread.setDaemon(true);
thread.start();
//主线程要干的操作,键盘输入导致线程阻塞
Scanner sc = new Scanner(System.in);
sc.next();
System.out.println("退出主线程"+Thread.currentThread().getName());
}
}
结果为:
控制台
d:/demo.text
使用jstack生成线程快照
//在jdk的文件夹 lib下,有jstack.exe(命令行工具)还有jvisualvm.exe(界面化工具)
jstack
作用:生成JVM当前时刻线程的快照(threaddump,即当前进程中的所有的线程信息)。
目的:帮助定位程序问题出现的原因,如长时间停顿、CPU占用率过高等。
常用参数:(以下参数可有可无)
-F jstack [-l] pid无法响应时,强制打印堆栈
-l l长列表. 打印关于锁的附加信息,例如属于java.util.concurrent的ownable synchronizers列表.
-m 混合模式输出(包括java和本地c/c++片段)堆栈。
pid: java应用程序的进程号,可以通过来任务管理器获得;
executable:产生core dump的java可执行程序;
core:打印出的core文件;
remote-hostname-or-ip:远程debug服务器的名称或IP;
server-id: 唯一id,假如一台主机上多个远程debug服务;
补充:
为了保证程序的可扩展性,建议使用实现Runnable接口的这种方式创建线程
1.程序中的同一资源指的是同一个Runnable对象,,,如果把其他类的对象也作为共享对象,那么继承Tread类和实现Runnable接口,是都可以处理同一资源的
2.安全的卖票程序中需要加入同步(Synchronized)