之前梳理了进程和线程的概念以及如何创建、结束进程和线程,本文总结一下线程使用过程中的知识点:
生命周期及状态变迁
线程的生命周期包括:新建、就绪、运行、阻塞、死亡这几个状态。
当线程被new之后,处于新建状态,并不是立马就被执行了,此时线程被虚拟机分配了内存以及初始化了线程内的变量。
调用thread对象的start方法时,线程处于就绪状态,就绪的线程并不一定立马就被执行,仅仅只是创建了方法栈和计数器,需要等待JVM根据优先级、算法等规则调度之后才会进入运行状态。
线程运行之后,因为前文说过的,多个线程之间通过轮流切换的方式使用CPU资源,所以JVM将CPU资源调度给别的线程后,当前线程就从运行状态转为就绪状态。
当线程调用sleep方法、或者执行一个阻塞时的IO方法且没有返回结果时、调用了suspend(挂起)方法、调用一个同步对象但当前对象正在被其他线程锁持有时,线程就会进入阻塞状态。
上文说到的几种方法,可以使线程进入死亡状态,该线程就结束了。
状态过程相关注意事项
- 线程只能调用start方法,不能调用run方法,调用run方法只是将thread对象当作一个普通的对象来运行。
- 除了新建状态外,其他状态不能再调用start方法,否则会抛出IllegalThreadStateException异常。
- Thread.sleep()方法会使当前正在被执行的线程睡眠,让CPU可以去启动另一个处于就绪的线程,但是该方法在睡眠期间被唤醒会抛出InterruptedException异常。(上文停止线程也用到该原理)
- 当时sleep时间结束后、调用的IO方法等待返回结果时、获取同步锁对象时、挂起的线程调用了resume方法时该线程就出于就绪状态,等待CPU调度。
- 主线程执行完之后,不影响其他线程的运行,每一个线程运行起来后,级别相同。
- 线程分守护线程和普通线程,守护线程时JVM自己使用的线程,如垃圾回收线程,用户创建的线程基本上为普通线程,也可以自动设置Thread对象的setDaemon(true)将线程改为守护线程(在start之前调用)。Thread对象的isDaemon方法可以查询是否时守护线程。
- 用户创建的线程设置为守护线程后,不管是否执行完,当进程中所有普通线程执行完,整个进程也就结束。
public class ThreadTest {
public static void main(String[] args) {
FutureTask futureTask = new FutureTask(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
sleep(50000);
System.out.println("FutureTask 等待50秒后返回100");
return 100;
}
});
Thread thread = new Thread(futureTask, "futureTask");
thread.setDaemon(true);
thread.start();
System.out.println("主线程执行完毕");
}
}
//执行结果:
主线程执行完毕
Process finished with exit code 0
- Thread对象的setPriority(int newPriority)可以设置线程优先级,范围从0~10,值越大,级别越高,级别越高,执行的机会就越多,不设置的话,默认级别和其父进程级别相同。
线程sleep和yield方法区别
- sleep方法会让线程进入阻塞状态,在其睡眠时间内,该线程不会被执行。
- yield方法只是将线程处于就绪状态,可能调度器又立马调度了该线程。
- yield方法暂停后,和该线程优先级相同或者高的线程优先执行。
- sleep之后,其他线程执行的机会有JVM决定,不一定就是高优先级的。
- yield不会抛出异常,sleep会抛出InterruptedException异常。
public class ThreadTest {
public static void main(String[] args) {
SellRunnable sellRunnable = new SellRunnable();
Thread thread1 = new Thread(sellRunnable, "1");
thread1.setPriority(1);
Thread thread2 = new Thread(sellRunnable, "2");
thread2.setPriority(5);
Thread thread3 = new Thread(sellRunnable, "3");
thread3.setPriority(5);
thread2.start();
thread1.start();
thread3.start();
}
}
class SellRunnable implements Runnable {
//有十张票
int index = 10;
public synchronized void sell() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(index>=1){
index--;
System.out.println("售货窗口:" + Thread.currentThread().getName() + " 卖出了一张票,剩余:" + index);
}else{
System.out.println("售货窗口:" + Thread.currentThread().getName() + " 买票时没票了");
}
}
@Override
public void run() {
while (index > 0) {
System.out.println("售货窗口:" + Thread.currentThread().getName() + " 开始买票");
sell();
}
}
}
//执行结果:
售货窗口:2 开始买票
售货窗口:3 开始买票
售货窗口:1 开始买票
售货窗口:2 卖出了一张票,剩余:9
售货窗口:2 开始买票
售货窗口:1 卖出了一张票,剩余:8
售货窗口:1 开始买票
售货窗口:3 卖出了一张票,剩余:7
售货窗口:3 开始买票
售货窗口:3 卖出了一张票,剩余:6
售货窗口:3 开始买票
售货窗口:1 卖出了一张票,剩余:5
售货窗口:1 开始买票
售货窗口:2 卖出了一张票,剩余:4
售货窗口:2 开始买票
售货窗口:2 卖出了一张票,剩余:3
售货窗口:2 开始买票
售货窗口:2 卖出了一张票,剩余:2
售货窗口:2 开始买票
售货窗口:2 卖出了一张票,剩余:1
售货窗口:2 开始买票
售货窗口:2 卖出了一张票,剩余:0
售货窗口:1 买票时没票了
售货窗口:3 买票时没票了
Process finished with exit code 0 //可以看出,2、3执行的相对较多
//加入sleep
@Override
public void run() {
while (index > 0) {
try {
Thread.sleep(100);
System.out.println("售货窗口:" + Thread.currentThread().getName() + " 开始买票");
sell();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//执行结果:
售货窗口:1 开始买票
售货窗口:2 开始买票
售货窗口:3 开始买票
售货窗口:1 卖出了一张票,剩余:9
售货窗口:3 卖出了一张票,剩余:8
售货窗口:2 卖出了一张票,剩余:7
售货窗口:3 开始买票
售货窗口:2 开始买票
售货窗口:1 开始买票
售货窗口:3 卖出了一张票,剩余:6
售货窗口:1 卖出了一张票,剩余:5
售货窗口:2 卖出了一张票,剩余:4
售货窗口:3 开始买票
售货窗口:2 开始买票
售货窗口:1 开始买票
售货窗口:3 卖出了一张票,剩余:3
售货窗口:1 卖出了一张票,剩余:2
售货窗口:2 卖出了一张票,剩余:1
售货窗口:3 开始买票
售货窗口:2 开始买票
售货窗口:1 开始买票
售货窗口:3 卖出了一张票,剩余:0
售货窗口:1 买票时没票了
售货窗口:2 买票时没票了
Process finished with exit code 0 // sleep之后,各个窗口差不多
//加入yield
@Override
public void run() {
while (index > 0) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("售货窗口:" + Thread.currentThread().getName() + " 开始买票");
sell();
if ("2".equals(Thread.currentThread().getName())) {
Thread.yield();
}
}
}
//执行结果:
售货窗口:2 开始买票
售货窗口:3 开始买票
售货窗口:1 开始买票
售货窗口:2 卖出了一张票,剩余:9
售货窗口:2 开始买票
售货窗口:1 卖出了一张票,剩余:8
售货窗口:1 开始买票
售货窗口:3 卖出了一张票,剩余:7
售货窗口:1 卖出了一张票,剩余:6
售货窗口:3 开始买票
售货窗口:1 开始买票
售货窗口:3 卖出了一张票,剩余:5
售货窗口:3 开始买票
售货窗口:2 卖出了一张票,剩余:4
售货窗口:2 开始买票
售货窗口:3 卖出了一张票,剩余:3
售货窗口:3 开始买票
售货窗口:1 卖出了一张票,剩余:2
售货窗口:1 开始买票
售货窗口:3 卖出了一张票,剩余:1
售货窗口:3 开始买票
售货窗口:2 卖出了一张票,剩余:0
售货窗口:3 买票时没票了
售货窗口:1 买票时没票了
Process finished with exit code 0 // 2变少,相对3变多(执行太快,yield看不出效果)
以上!
进程与线程的创建和销毁
线程知识点总结
线程同步