线程
线程 & 进程
学过操作系统的基本都知道这个词。一开始出现的是进程,进程是操作系统中分时系统的一个基本运作单位。但是后来由于系统设计的升级,对进程又进行了划分,所以出现了线程。
进程可以包含多个线程,至少一个。这样就出现了线程来执行用户需要执行的命令。
区别总结一下:
- 进程是一段正在执行的程序,是资源分配的基本单元,而线程时CPU调度的基本单元。
- 进程间相互独立,进程与进程之间不能共享资源,一个进程至少有一个线程,同一个进程的各线程共享整个进程的资源(寄存器,堆栈,上下文)。
- 线程的创建和切换开销比进程小。
JAVA中的线程
高级语言都有一套使用线程的方法。在JAVA中最原始的方法就2种:
- 继承Thread
- 实现Runnable接口
当然除了这里的最原始的方法,还有一些方法可以创建线程。
线程的状态
线程状态大致可以分为5种:
初始(NEW)
:新创建了一个线程对象,但还没有调用start()方法。可运行(RUNNABLE)
:也称为就绪状态,调用start方法后线程就计入就绪状态但并不是说只要调用start方法线程就马上变为当前线程,在变为当前线程之前都是就绪状态。线程在睡眠和挂起中恢复的时候也会进入就绪状态。
线程对象创建后,其他的线程调用了它的start方法,这个线程就会进入可运行线程池中,等待被调度选中,获取CPU的使用权。运行(RUNNING)
:可运行状态(RUNNABLE)的线程获得了CPU的时间片,执行程序代码,开始执行run
。阻塞(BLOCKED)
:线程由于某种原因放弃了CPU的使用权,也就是时间片,暂时停止了运行。
a.等待阻塞
: 通过调用线程的wait方法,让线程等待某工作的完成,线程进入等待队列(waitting queue)。
b.同步阻塞
: 线程在获取synchronized失败,会进入同步阻塞状态,线程进入阻塞。
c.其他阻塞
: 通过调用线程的sleep或者join或发出了I/O请求,线程会进入阻塞。当sleep超时,join等待线程终止或超时,或I/O完毕,线程就重新进入可运行状态(RUNNABLE)
死亡(DEAD)
:线程执行结束,run,main方法结束,或者因为异常退出了run,该线程结束生命周期,状态不可逆转。
当然在JDK中,线程的状态有NEW
,RUNNABLE
,BLOCKED
,WAITING
,TIMED_WAITING
,TERMINATED
,和系统层面的划分差不多。
等待(WAITING)
:进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。限时等待(TIMED_WAITING)
:该状态不同于WAITING,它可以在指定的时间后自行返回。终止(TERMINATED)
:表示该线程已经执行完毕。
线程状态图
初始状态:
实现了Runnable接口或者继承了Thread的一个类,通过new创建了实例,线程就进入了初始状态。
可运行状态:
- 可运行状态只是代表该线程在随时等待调度程序。
- 调用线程的Start方法,线程进入可运行状态。
- 当前线程sleep结束,其他线程join结束,IO结束,拿到了对象锁,线程也可以进入可运行状态。
- 当前线程时间片用完后,调用了该线程的yield方法,该线程进入可运行状态。
运行状态:
进入运行状态RUNNING,只有等到被调度程序选中,才能够进入。
死亡状态:
- 当线程run完成时,或者main方法完成,就认为dead。
- 如果尝试在一个dead的线程上start,会抛出IllegalThreadStateException
阻塞状态:
- 当前线程调用了sleep方法,当前线程进入阻塞。
- 运行在当前线程里的其他线程调用了join方法,当前线程进入阻塞
- 等待IO时,当前线程进入阻塞。
有很多的博客,提到了一些锁池,等待队列,等待池,可运行池的概念,但是我一直没有找到这些名词的来源,没有哪本书中,提过这些名词。但是通过这些名词,解释了一些可能不容易理解的问题。
从一个国外站上面看到一个文章说是这样解释的:
Inside the Java Virtual Machine
在JAVA中,每个对象都有一个内部锁(Monitor)。JVM会为每个对象维护2个区域,Entry Set(入口集),Wait Set(等待集)。Entry Set存储了等待获取object的内部锁的所有线程,Wait Set存储了执行了object.wait方法的线程。
如果有个线程A执行了wait,那么A就会被暂停,状态变为WAITTING,进入Wait Set,我们可以称A为object的等待线程,当其他线程执行了object.notify或者notifyAll,Wait Set中的任意一个线程会被唤醒进入RUNNABLE,并且会参与和Entry Set中的线程一起争夺monitor。如果wait set的线程成功拿到了锁,这个线程就可以从Wait Set移除,否则该线程还会留在Wait set,并且再次暂停,等待下次申请锁的机会。
线程的方法
Thread.Sleep(long millis)
sleep属于线程的方法,调用后当前线程进入阻塞,但是不会释放锁
,millis之后,线程会苏醒进入可运行状态RUNNABLE
。
Thread.yield()
当前线程调用该方法,当前的线程就会放弃CPU,由RUNNING-->RUNNABLE
,让相同优先级的线程轮流执行,但是不一定保证实际会轮流执行。
当然该线程也有可能会被调度程序再次选中,yield不会导致阻塞。
t.join/t.join(long millis)
当前线程t1里调用其他线程t2的join方法,当前线程实际进入wait ,直到t2执行完毕或者join时间到了,t1被可能唤醒进入可运行状态。
object.wait()
当前线程调用object的wait方法,当前线程释放对象锁,只能等notify,notifyall唤醒或者wait时间到唤醒。
object.notify
唤醒在这个对象锁上面的某一个线程,notifyAll唤醒这个对象锁上面的所有线程。