Java线程基础
什么是线程
线程是CPU调度的最小操作单元,进程有线程组成,一个进程至少有一个线程组成。在Java中,我们启动一个main函数就是创建了一个进程,这个进程也称为主线程。一个进程中可以存在多个子线程,这些子线程共享进程的堆和方法区,每个线程都有程序计数器和栈。程序计数器和栈有什么用呢?程序计数器是一块内存区域,主要用来记录程序当前要执行指令的地址。我们知道线程的运行是需要获取CPU时间片的,CPU一般是时间片轮转的方式运行,如果这个线程的CPU时间片用完了,但是程序没有执行完成,那么这个线程如何知道下次执行从哪里开始呢,其实程序计数器就是为了记录该线程让出CPU时的指令地址的,等再次分配到CPU时间片后,该线程会从程序计数器指定的地址继续执行。栈,每个线程都有自己的栈资源,用来存放局部变量,这些局部变量是线程私有的,其他线程不能访问。
线程的创建与运行
(1)线程创建的几种方式:继承Thread类,实现Runable接口,实现Callable接口,线程池
(2)线程是如何运行的?重写run方法,调用线程实现类的start方法就会创建线程执行run方法的代码逻辑。注意,如果直接调用run方法的话,JVM只会当成普通方法执行,不会创建新线程运行run方法的代码逻辑。
(3)线程的运行状态:NEW,RUNNABLE、BLOCKED、WATTING、TIMED_WATTING、TERMINATED
线程的通知与等待
(1)基本方法:wait()、notify()、notifyAll()、join()、sleep()、yield()
(2)Wait()函数:暂停线程,线程进入WATTING或TIMED_WATTING状态,释放线程资源,必须等待其他线程调用notify()、notifyAll()函数将其唤醒或者其他线程调用该线程的中断方法。必须获取到该对象的监视器锁才能调用,否则运行时会抛出异常。
(3)Notifty()函数:一个线程调用notify后,会唤醒一个在该共享变量因调用wait函数而挂起的线程,因为一个共享变量可能会挂起多个线程,所以具体唤醒哪个线程是随机的。被唤醒后的线程也不一定立刻执行,还需要和其他线程竞争锁,只有获取到锁才可以继续执行。
(4)NotiftyAll()函数:唤醒所有在该共享变量因为调用wait函数而挂起的线程。
(5)什么是虚假唤醒?一个线程可以从挂起状态变为可运行状态,但是它并没有被其他线程调用notify()/notiftAll()进行通知,或被中断,等待超时。这就是所谓的虚假唤醒。
(6)可能导致虚假唤醒的场景:
①notifyAll()的过度唤醒;
②底层系统或JVM的优化;
③多条件变量共享同一把锁
等待线程执行终止的join方法
(1)Join():等待线程执行到终止的方法,在main主线程里面调用另一个线程的join()方法,只有等待另一个线程执行到终止才会继续执行main主线程。
让线程睡眠的sleep方法
(1)Sleep()函数:暂停线程,线程进入TIMED_WATTING状态,不会释放锁。
让出CPU执行权的yield方法
(1)Yield()函数:线程的CPU时间片是由CPU同意分配的,只有等待一个线程的CPU时间片全部用完,CPU才会进行下一轮的的线程调度,线程调用yield()函数后,是在这个线程还没有用完时间片的情况下主动请求CPU可以进行下一轮的线程调度了,这时线程会让出CPU时间片,然后处于就绪状态,CPU会从就绪队列里面选取优先级最高的线程执行,但是也有可能选取到刚才主动给让出CPU时间片的线程。
线程中断
(1)基本方法:void interrupt()、boolean isInterrupted()、boolean interrupted()
(2)void interrupt():中断线程。调用方法后,该线程会被打上中断标志,但是并不会立刻中断,线程还会继续执行,如果线程A应为调用wait、join、sleep函数而阻塞挂载起来,另一个线程再调用A的void interrupt()方法,会抛出异常。
(3)boolean isInterrupted():检测当前线程是否被中断,如果是返回true,否则返回false。不会清楚中断标志。
(4)boolean interrupted():检测当前线程是否被中断,如果是返回true,否则返回false。如果线程被中断则会清除中断标志。
理解线程的上下文切换
(1)同一时刻CPU只能执行一个线程,CPU的资源分配采用了时间片轮转的策略,当前线程时间片用完后,线程会进入就绪状态并让出CPU给其他线程占用,这就是上下文切换。
(2)线程上下文切换的时机,线程消耗完时间片处于就绪状态或线程被其他线程中断。
什么是线程死锁
(1)死锁概念:两个或多个线程因为抢夺对方所占用的资源而造成的无限期等待的现象,没有外力干扰的状态下,会一直等待无法继续运行下去。
(2)死锁产生的条件:
①互斥条件:该共享资源具有排他性,一次只能有一个线程占用,其他线程如果想占用,只能等待占用资源的线程释放。
②请求并持有条件:一个线程已经持有了至少一个资源,此时该线程继续请求其他资源,但是该资源一被其他线程占用,造成线程阻塞但他又不释放自己已经获得的资源。
③不可剥夺条件:指线程自己获取到的资源再自己使用完之前都不释放,只有自己使用完毕后才由自己释放该资源。
④环路等待条件:多个线程造成的循环依赖,各个线程等待下一个线程的资源释放造成的无限期等待。
10.如何避免线程死锁
(1)死锁产生的4个条件,只需要至少破坏一个就可以避免线程死锁。目前只有请求并持有和环路等待条件可以被破坏。