这一块内容主要参考<Java核心技术> 第10版 .
进程与线程
我们用电脑时打开任务管理器,切换到进程选项卡,里面看到的各种xxx.exe,每一个都是一个进程,而多个线程则构成了一个进程.在这篇博客之前的代码中,都是单线程的--一个main()线程,在抛出异常的时候能看到Exception in thread "main"...类似字样.
线程状态
新建状态
执行new Thread()后,调用start()之前时,这个线程就是新建状态.
public class NewThread {
public static void main(String[] args) {
Test t1 = new Test();
//t1.start();
}
}
class Test extends Thread{
@Override
public void run(){}
}
就绪状态
对一个线程调用start()之后,就进入了就绪状态,此时线程要做的事情就是等待CPU调度它:获取时间片之后便可以运行.
t1.start();
下面是start()的代码:
public synchronized void start() {
if (threadStatus != 0) //线程状态不是"0",即'not yet started'(源码对该变量的解释).
throw new IllegalThreadStateException(); //抛出非法线程状态异常.
boolean started = false; //已开始设置为false.
try {
start0(); //私有本地方法start0().
started = true; //已开始设置为true.
} finally {
try {
if (!started) { //开启失败
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
调用之后start()结束.
下面展示了一种会抛出IllegalThreadStateException的情况,即对一个线程调用两次start():
public static void main(String[] args) {
Task task = new Task();
task.start();
task.start();
}
运行结果:
这是前台线程.
Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.start(Thread.java:708)
at thread.Illegal.main(Illegal.java:7)
运行状态
此时该线程得到了时间片,run()开始执行,但是需要注意的是,线程不是持续运行的,只有获得时间片才能运行,也就是说CPU如果后续没有给它时间片,线程就会进入阻塞状态.宏观上是持续运行,微观上是断续运行的.
下面是Thread中的run():
public void run() {
if (target != null) {
target.run();
}
}
target是一个Runnable对象,只有一个run(),一个线程可以继承Thread,也可以实现Runnable接口:
public interface Runnable {
public abstract void run();
}
值得注意的是,run()不能抛出异常,所以重写run()时,假如需要处理异常,只能使用try/catch/finally.
阻塞状态
阻塞状态分为多种情况.
1.等待阻塞
调用对象的wait(),此线程会被放入等待池.
2.同步阻塞
即synchronized关键字.如果想要获得此同步锁对象,但是这个锁仍被其他线程占用时,该线程就会进入锁池.
3.其他阻塞
当遇到Thread.sleep(),或有其他线程的插入即join(),此线程放弃时间片段,进入阻塞状态.
死亡状态
线程run()执行完毕,这个线程就进入了死亡状态.
线程属性
线程优先级
我们在任务管理器,右键点击一个进程,选择设置优先级,可以看到六个选项:
在Java中线程有10个优先级,我们也可以设置优先级:
t1.setPriority(int newPriority);
范围是1~10,有三个常量:
public final static int MIN_PRIORITY = 1; //最低
public final static int NORM_PRIORITY = 5; //标准
public final static int MAX_PRIORITY = 10; //最高
越低表示被调度到的可能性越低,最高不代表一定会被调度到,只是可能性最高:
public class NewThread {
public static void main(String[] args) {
//创建1-10十个线程.
Thread t1 = new Task(1);
......
Thread t10 = new Task(10);
//将线程优先级分别设置为1-10.
t1.setPriority(Thread.MIN_PRIORITY); //1
......
t5.setPriority(Thread.NORM_PRIORITY); //5
......
t10.setPriority(Thread.MAX_PRIORITY); //10
//启动
t1.start();......t10.start();
}
}
class Task extends Thread{
private int pro;
Task(int pro) {
this.pro = pro;
}
@Override
public void run() {
System.out.println("该线程的优先级为" + pro + ".");
}
}
其中一次运行结果:
该线程的优先级为1.
该线程的优先级为6.
该线程的优先级为2.
该线程的优先级为8.
该线程的优先级为5.
该线程的优先级为9.
该线程的优先级为3.
该线程的优先级为10.
该线程的优先级为4.
该线程的优先级为7.
可以看到运行顺序不是按照优先级高低的,再来看看下面的情况:
public static void main(String[] args) {
Thread t1 = new Task(1);
......
Thread t7 = new Task(1);
Thread t8 = new Task(5);
......
Thread t13 = new Task(5);
Thread t14 = new Task(10);
......
Thread t20 = new Task(10);
t1.setPriority(Thread.MIN_PRIORITY);
......
t7.setPriority(Thread.MIN_PRIORITY);
t8.setPriority(Thread.NORM_PRIORITY);
......
t13.setPriority(Thread.NORM_PRIORITY);
t14.setPriority(Thread.MAX_PRIORITY);
......
t20.setPriority(Thread.MAX_PRIORITY);
t1.start();......t20.start();
}
其中一次运行结果:
该线程的优先级为1.
该线程的优先级为5.
该线程的优先级为5.
该线程的优先级为10.
该线程的优先级为10.
该线程的优先级为10.
该线程的优先级为1.
该线程的优先级为5.
该线程的优先级为1.
该线程的优先级为1.
该线程的优先级为10.
该线程的优先级为10.
该线程的优先级为10.
该线程的优先级为10.
该线程的优先级为5.
该线程的优先级为5.
该线程的优先级为1.
该线程的优先级为5.
该线程的优先级为1.
该线程的优先级为1.
所以设置高优先级只能是被运行的概率高,所以在不必需的时候就不要设置优先级.
守护线程
顾名思义,守护其它线程,主要是提供一些辅助功能,比如计时.当虚拟机中只剩守护线程时,虚拟机就会退出:
守护线程应该永远不去访问固有资源,如文件,数据库,因为它会在任何时候甚至在一个操作的中间发生中断.
将一个线程设置为守护线程的方法是对它调用setDaemon(),并传入true作为参数:
public class NewThread {
public static void main(String[] args) throws InterruptedException{
Daemon daemon = new Daemon();
Task task = new Task();
daemon.setDaemon(true);//设置为守护线程.
daemon.start(); //启动.
Thread.sleep(5000); //5秒之后启动Task线程.
task.start();
}
}
class Daemon extends Thread {
static int seconds = 1;
@Override
public void run() {
for (int i = 0; i < 15; i++) { //正常来说该线程应该在15秒后结束
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("守护线程已经运行" + seconds + "秒.");
seconds ++;
}
}
}
class Task extends Thread {
@Override
public void run() {
System.out.println("这是前台线程.");
}
}
在不对Daemon对象调用setDaemon(true)时,这个程序会从1秒一直运行到15秒,而调用了之后,在前台线程启动并运行完毕之后(5秒多一点点),该线程就会被终止:
守护线程已经运行1秒.
守护线程已经运行2秒.
守护线程已经运行3秒.
守护线程已经运行4秒.
守护线程已经运行5秒.
这是前台线程.
Process finished with exit code 0