目录
1.线程的状态
线程是操作系统调度的基本单位,线程的状态是一个枚举类型
通过遍历可以打印得到线程的状态的名称
public class ThreadDemo12 {
public static void main(String[] args) {
for (Thread.State state : Thread.State.values()
) {
System.out.println(state);
}
}
}
Java中对线程的状态做了细化,一共有六个状态
1.1 NEW
仅仅只创建了Three对象,还没调用start方法,内核没有创建对应的PCB
1.2 TERMINATED
表示内核中的PCB已经执行完了,但是Thread对象还在
1.3 RUNNABLE
可运行的,包含正在CPU上执行的,或者正在就绪队列中,随时可以取CPU上执行的
1.4 WAITING
表示线程阻塞
1.5 TIMED_WAITING
表示线程阻塞
1.6 BLOCKED
表示线程阻塞
后三个状态是不同原因的阻塞
2.线程状态的转换
线程状态的大致转换如图
主线是由NEW->RUNNALBE->TERMINATED,从线程创建到线程执行再到线程结束
支线就是在三种不同的操作下引起的阻塞状态,是在RUNNABLE状态时进行的
下面结合代码可以直观地看到线程的状态
public class ThreadDemo12 {
public static void main(String[] args) {
Thread t = new Thread(()->{
for (int i = 0; i < 10000000; i++) {
}
});
System.out.println("t.start之前:"+t.getState());
t.start();
try {
t.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("t.start之后:"+t.getState());
}
}
TERMINATED的存在是因为内核线程释放的时候,不能保证线程对象也立即释放,因此存在线程释放后线程对象还存在,生命周期是不一致的,所以需要通过一个特定的状态来将线程对象标识成无效,不能再使用,也不能重新start,一个线程只能创建一次PCB
线程结束后,无法再利用多线程了,但是线程对象还存在,还可以用线程对象调用一些方法和属性
接下来查看RUNNABLE状态:
这里能看到RUNNABLE状态是因为run方法中没有sleep之类的方法
如果run方法中有sleep,那么线程的状态就是RUNNABLE或者TIMED_WAITING,取决于线程运行到哪个环节了
public static void main(String[] args) {
Thread t = new Thread(()->{
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
System.out.println("t.start之前:"+t.getState());
t.start();
for (int i = 0; i < 1000; i++) {
System.out.println("线程执行中的状态:"+t.getState());
}
try {
t.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("t.start之后:"+t.getState());
}
run方法中加sleep后,循环打印start后线程状态的结果
多次获取后我们看到线程状态从RUNNABLE转换到TIMED_WAITING,当前获取到的状态是什么,完全取决于操作系统的调度,获取状态的瞬间,线程对象是在执行还是在休眠!!
还有两种阻塞I(WAITING/BLOCKED)状态后续写到对应方法再进行叙述
3.多线程的意义
通过下面代码感受多线程和单线程之间执行速度的差别
程序分为cpu密集和IO密集,cpu密集包含大量的加减乘除运算,IO密集涉及到读写文件,读写控制台,读写网络,这里我们通过cpu密集的程序来感受多线程的特点
假设当前有两个变量需要把他们各自自增1000W次,分别通过单线程和多线程实现
可以通过一个线程,先对a自增,后对b自增
也可以多线程,分别对ab自增
先看单线程执行时间
public class ThreadDemo13 {
public static void main(String[] args) {
serial();
}
public static void serial(){
long beg = System.currentTimeMillis();
long a = 0;
for (long i = 0; i < 100_000_0000L; i++) {
a++;
}
long b = 0;
for (long i = 0; i < 100_000_0000L; i++) {
b++;
}
long end = System.currentTimeMillis();
System.out.println("执行时间: "+(end-beg)+" ms");
}
}
单线程多次运行后,执行的时间
public static void concurrency(){
Thread t1 = new Thread(()->{
long a = 0;
for (long i = 0; i < 100_0000_0000L; i++) {
a++;
}
});
Thread t2 = new Thread(()->{
long b = 0;
for (long i = 0; i < 100_0000_0000L; i++) {
b++;
}
});
long beg = System.currentTimeMillis();
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
long end = System.currentTimeMillis();
System.out.println("并发执行耗时:"+(end-beg)+" ms");
}
注意:如果在调用两个start后没有加join方法等待t1t2线程执行结束,那么执行时间就会使0ms
因为当主线和t1t2线程并发执行时,谁先执行是不确定的,如果刚调完t1t2,main线程就继续执行,那么打印的时间就是0,因此要先让两个线程执行结束,再继续进行主线程
在主线程调用join方法只能保证主线程是最后结束的,另外的两个线程执行的顺序和谁先结束谁后结束是不确定的
并发执行后,多次执行的时间
多线程执行的时间相比于串行执行时间,明显缩短了,为什么没有缩短一半呢?
因为保证不了并行执行!在执行过程中会经历很多次的调度,过程中可能是并发执行(同一时间间隔上在一个核心上执行)或者并行执行(在多个核心上同时执行)
线程调度自身也有时间消耗,因此很难让时间正好是串行调度的一半,即使是这样,多线程执行任然有很大的意义
多线程在cpu密集型的任务中,有非常大的作用,可以充分利用cpu的多核资源,从而加快程序的运行效率
也不是多线程就一定能提高效率,要看是否是多核,二是当前核心是否空闲,如果cpu当前已经满载了,使用多线程也不能提高效率
在IO密集型任务中.经常看到"程序未响应".比如启动一个比较大的游戏的时候,要加载大量的数据文件,涉及到大量的读硬盘操作,阻塞了界面响应,多线程也是能起到一定的改善作用(让一个线程负责IO,另一个线程来响应用户的操作)