Thread方法
构造函数
方法名 | 说明 |
---|---|
Thread(String name) | 构建线程时可以给线程起名 |
Thread(Runnable runnable String name) | 传入runnable实例并取名 |
其他方法
先列一个表,后面一一介绍
方法 | 获取属性 |
---|---|
getId() | ID(身份标识) |
getName() | 名称 |
getState() | 状态 |
getPriority() | 优先级 |
isDaemon() | 是否时后台线程 |
isAlive() | 是否存活 |
isInterrupted() | 是否被中断 |
ID
我们的线程有许多的ID,此处的ID指的是JVM中为线程指定的ID,此外还有操作系统线程API中的ID,还有PCB中的ID
名称
就是我们刚才讲的,在创建线程时为线程起的名字
优先级
就是线程执行的优先程度
后台线程
如果线程是前台线程,那么main函数执行完,进程会等线程运行结束后再退出(main也是前台进程),我们对一些不重要的数据(可以模棱两可的)可以使用后台进程,以减少系统资源的消耗,例如记录步数
是否存活
Thread类对象和真实的系统内核虽然是一一对应的关系,但是并不是同生共死的,当我们的Thread类创建出来时,内核线程并没有创建,而是等Thread调用start方法才创建线程,而当run执行完成后,内核线程就销毁了,但是Thread对象还有,因此我们调用isAlive()判断线程的真实状态
start和run区别
直接调用run方法并不会创建线程,而是单纯的运行其内部的代码,而调用start才会创建线程,然后和其他的线程并发执行
中断
一般来说,我们的线程会等run执行完后才会结束,我们可以通过下面这个办法来提前中断线程
thread.interrupt();
我们的isInterrupt()方法就是判断这个interrupt背后的标识位是否为true
然而设置interrupt也有例外,当我们的线程正在sleep状态,那么不会设置标志位为true,而是触发异常(InterruptedException),该异常会让sleep的线程提前唤醒
join
当我们调用t.join()时,我们的main就会阻塞等待,直到t线程执行完毕,而如果在调用join时,t线程已经执行完毕,那么就不用阻塞等待了
join()还可以传参数,其参数是时间,当我们等到这个时间到了的时候,线程就不再阻塞等待了
sleep
调用这个方法时,线程的PCB就不在链表中了,而是进入一个阻塞队列,直到到了时间再回到链表中
当然,并不是一唤醒就可以直接到CPU上执行工作,而是看调度的时间开销。也有一些操作系统通过一些计算是可以做到实时调用的,例如发火箭就需要这个系统
状态
以下的状态是Java对于线程状态的描述,并不是PCB中的状态
- NEW Thread对象创建完毕,但是PCB没有创建
- TERMINATED PCB被销毁,Thread对象还未销毁
- RUNNABLE 正在运行或者在就绪队列中
- TIMED_WAITED 调用sleep,join,阻塞中
- WAITING 调用wait,另一种阻塞,之后讲
- BLOCKED 等待锁时的阻塞,之后讲
线程安全
当我们让两个线程同时对一个数据进行多次自增操作时,可能会出现线程不安全的情况,这个情况就是最终加出来的数是随机的,每一次运行的结果都有可能不一样
这个结果出现的原因就是我们的自增操作并不是原子性的,即他是分成三条语句执行的,先将数据从内存读到CPU,再在寄存器中加一,然后再写回到内存中,而当我们双线程进行多次自增时,可能会出现类似于MySQL中的脏读问题
例如,我们理想情况下,count是1,A线程先读是1,然后自增,然后A放回,count变为2,B线程读count是2,B再放回是3.
但有可能出现下面的情况
A线程先读是1,然后自增,这个时候B线程也读count是1,然后A放回,count变为2,B再放回还是2.
至于如何解决这种问题,我们稍后的blog会进行讲解