前言
对于多线程而言,每到面试季都会被面试官反复拉出来询问,不仅能体现你的日常学习的深度,还可以对你的基础有个大体的了解,打算出一系列关于多线程方面的文章,也算是自我的总结。
线程的状态
首先来看一张图(忘记哪里偷的啦)
然后以这张图来进行展开:
对于线程的状态:
- 新生(通过 new关键字创建一个线程)
- 就绪 (通过start关键之让线程进入就绪的状态)
- 运行 (通过CPU的调用进入到 运行状态)
- 阻塞 (线程在运行的过程中遇到了sleep(睡眠) yield(让步)wait(等待)会进入到阻塞的状态)有三种。
- 等待阻塞(o.wait->等待对列):
运行(running)的线程执行 o.wait()方法,JVM 会把该线程放入等待队列(waitting queue)
中。 - 同步阻塞(lock->锁池)
运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线
程放入锁池(lock pool)中。 - 其他阻塞(sleep/join)
运行(running)的线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时,
JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O
处理完毕时,线程重新转入可运行(runnable)状态。
- 死亡(也有三种的情况)
正常结束
- run()或 call()方法执行完成,线程正常结束。
异常结束 - 线程抛出一个未捕获的 Exception 或 Error。
调用 stop - 直接调用该线程的 stop()方法来结束该线程—该方法通常容易导致死锁,不推荐使用。
各种状态下的问题
sleep和wait的区别
我们都知道的是对于sleep和wait都是会让线程出现暂停执行的状态,下面从几个方面进行剖析个体区别
- 对于 sleep()方法,我们首先要知道该方法是属于 Thread 类中的。而 wait()方法,则是属于
Object 类中的。 - sleep()方法导致了程序暂停执行指定的时间,让出 cpu 该其他线程,但是他的监控状态依然
保持者,当指定的时间到了又会自动恢复运行状态。 - 在调用 sleep()方法的过程中,线程不会释放对象锁。
- 而当调用 wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此
对象调用 notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。 - 使用的位置不同:对于wait来说使用之前要获取到锁的存在,所以必须放在同步代码,或者同步块中进行执行 但是 sleep来说可以放在任何的地方执行 。
- sleep需要捕获异常 。wait notify 等不需要这些。
start 和run的区别
- start()方法来启动线程,真正实现了多线程运行。这时无需等待 run 方法体代码执行完毕,
可以直接继续执行下面的代码。 - 通过调用 Thread 类的 start()方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运
行。对于多线程来说只有真正意义上调用了start方法才算是对于线程的一个启动。 - 方法 run()称为线程体,它包含了要执行的这个线程的内容,线程就进入了运行状态,开始运
行 run 函数当中的代码。 Run 方法运行结束, 此线程终止。然后 CPU 再调度其它线程。
join()
join()方法使调用该方法的线程在此之前执行完毕,也就是等待该方法的线程执行完毕后再往下继续执行。注意该方法也需要捕捉异常。
就是说让该线程在执行完RUN()方法以后再执行join方法后面的代码,就是说可以让两个线程合并起来,用于实现同步功能
yield()
该方法与sleep() 类似 只不过不能够由用户指定暂停多长的时间,并且yield ()方法只能让同优先级的线程有执行的机会。 前面提到了 sleep不会释放锁标识yield也不会释放锁标识。
实际上,yield()方法对应了如下操作;先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把CPU的占有权交给次线程,否则继续运行原来的线程,所以yield()方法称为“退让”,它把运行机会让给了同等级的其他线程。
sleep 方法允许较低优先级的线程获得运行机会,但yield()方法执行时,当前线程仍处在可运行状态,所以不可能让出较低优先级的线程此时获取CPU占有权。在一个运行系统中,如果较高优先级的线程没有调用sleep方法,也没有受到I/O阻塞,那么较低优先级线程只能等待所有较高优先级的线程运行结束,方可有机会运行。yield()只是使当前线程重新回到可执行状态,所有执行yield()的线程有可能在进入到可执行状态后马上又被执行,所以yield()方法只能使同优先级的线程有执行的机会。
wait()和notify()、notifyAll()
这三个方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块内使用。synchronized关键字用于保护共享数据,阻止其他线程对共享数据的存取,但是这样程序的流程就很不灵活了,如何才能在当前线程还没退出synchronized数据块时让其他线程也有机会访问共享数据呢?此时就用这三个方法来灵活控制。wait() 方法使当前线程暂停执行并释放对象锁标示,让其他线程可以进入synchronized数据块,当前线程被放入对象等待池中。当调用notify()方法后,将从对象的等待池中移走一个任意的线程并放到锁标志等待池中,只有锁标志等待池中线程能够获取锁标志;如果锁标志等待池中没有线程,则notify()不起作用。notifyAll() 从对象等待池中移走所有等待那个对象的线程并放到锁标志等待池中。
wait,notify阻塞唤醒确切过程?在哪阻塞,在哪唤醒?为什么要出现在同步代码块中,为什么要处于while循环中?
常见的 void wait 方法有
- wait( long timeout )
- wait()。
对于 无参的方法来说 : 在其他线程调用 此对象的notify 方法或者 nofifyall方法前 导致当前的线程处于等待的状态。
对于有参的函数来说 以上的两条成立的情况下 还会在时间超时之前也是处于等待的状态。
对于在执行完 wait方法以后。线程会释放掉所占用的锁标识 从而使线程所在的对象中的其他synchronized数据可被别的线程使用。 因为在执行wait 和 notify() 时候需要对锁标志进程处理和操作 一个是释放锁 一个是加锁 所以 就是来说 需要要在 synchronized函数中或者 函数块中进行调用,如果不在函数中 或是函数块中进行调用 虽然说可以编译通过。但是会出现IllegalMonitorStateException
异常。
wait,notify 和notifyAll 这些方法为什么不在 thread类里面
一个很明显的原因是Java提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程来获得 由于 wait notify和notifyAll 都是锁级别的的操作,所以把他们定义在Object类中因为锁属于对象。
后记
以上就是暂时出现的关于这些方法的一个小小讲解呀,有什么不得体的地方欢迎大家指出来呀。