线程的生命周期和常用办法
生命周期
根据jdk
官方文档,线程状况有以下几种
-
NEW
没有发动的线程处于此状况。 -
RUNNABLE
在Java虚拟机中履行的线程处于此状况。 -
BLOCKED
被堵塞等候监视器确定的线程处于此状况。 -
WAITING
正在等候另一个线程履行特定动作的线程处于此状况。 -
TIMED_WAITING
正在等候另一个线程履行动作到达指定等候时刻的线程处于此状况。 -
TERMINATED
已退出的线程处于此状况。
一个线程能够在给定时刻点处于一个状况。 这些状况是不反映任何操作系统线程状况的虚拟机状况。
如图示所见
代码演示
NEW / TIMED_WAITING / TERMINATED
package ThreadMethod;
import java.util.concurrent.TimeUnit;
public class ThreadState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
System.out.println("线程状况:"+ thread.getState());
thread.start();
Thread.sleep(2000);
System.out.println("线程状况:" + thread.getState());
Thread.sleep(2000);
System.out.println("线程状况:" + thread.getState());
}
}
成果
线程状况:NEW
线程状况:TIMED_WAITING
线程状况:TERMINATED
线程状况:TERMINATED
WAITING / BLOCKED
package ThreadMethod;
public class BlockThreadState implements Runnable {
static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
BlockThreadState blockThread = new BlockThreadState();
Thread thread = new Thread(blockThread);
System.out.println("thread state : " + thread.getState());
thread.start();
Thread.sleep(20);
System.out.println("thread state : " + thread.getState());
synchronized (lock){
lock.notify();
}
System.out.println("thread state : " + thread.getState());
Thread.sleep(20);
System.out.println("thread state : " + thread.getState());
}
@Override
public void run() {
try {
synchronized (lock){
// wait状况
lock.wait();
// synchronized重新拿到锁 处于block状况
for (int i = 0; i < 1000000000; i++) {
continue;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
成果
thread state : NEW
thread state : WAITING
thread state : BLOCKED
thread state : TERMINATED
常见办法
wait()
wait()办法履行后,会堵塞线程,同时开释锁,假如想要唤醒该线程,则需求以下条件
- 另一个线程调用这个目标的notify()办法,且刚好被唤醒的时本线程
- 另一个线程调用了这个目标的
notifyAll
()办法 - 过了wait(long timeout) 规定的超时时刻,假如传入0便是永久等候
- 线程本身调用了interrupt()
PS: 运用wait()办法时,必须先拥有monitor锁, 也便是说wait办法需求放在同步代码块中履行
notify/notifyAll
notify/notifyAll
用于唤醒线程,当另一个线程调用wait()进入 waitting
状况时,另一个线程调用notifyAll
()可唤醒当时线程(假如有多个线程,运用notify并不一定能够唤醒线程)
组合运用示例
public class BlockThread {
public static void main(String[] args) {
Message message = new Message();
// 创建一个等候线程
Thread waitThread = new Thread(new WaitThread(message));
// 创建一个唤醒线程
Thread notifyThread = new Thread(new NotifyThread(message));
// 发动等候线程和唤醒线程
waitThread.start();
notifyThread.start();
}
}
// 同享的音讯类
class Message {
private boolean isReady = false;
// 等候办法
public synchronized void waitForMessage() {
while (!isReady) {
try {
// 当音讯不行用时,线程进入等候状况
System.out.println("线程进入等候状况");
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 履行到这儿表明收到音讯,进行处理
System.out.println("收到音讯!");
}
// 唤醒办法
public synchronized void sendMessage() {
// 做一些准备工作,例如获取音讯
// 唤醒等候的线程
isReady = true;
System.out.println("唤醒等候的线程");
notify();
}
}
// 等候线程
class WaitThread implements Runnable {
private Message message;
public WaitThread(Message message) {
this.message = message;
}
@Override
public void run() {
// 等候音讯
message.waitForMessage();
}
}
// 唤醒线程
class NotifyThread implements Runnable {
private Message message;
public NotifyThread(Message message) {
this.message = message;
}
@Override
public void run() {
// 发送音讯
message.sendMessage();
}
}
/*
线程进入等候状况
唤醒等候的线程
收到音讯!
Process finished with exit code 0
*/
图示 monitor锁
- Entry Set 入口集
- 线程进入后抢锁
- The owner 锁持有线程
- 假如办法没有履行完之前没有开释锁,则程序正常退出并开释锁
- 假如办法没有履行完之前开释了锁如调用了wait()办法,则程序再次进入等候集进行抢锁
- Wait Set 等候集
- 假如上方入口集,不同点是等候集在履行办法锁时中途开释了锁
wait、notify 完成出产者消费者模式
import java.util.LinkedList;
import java.util.List;
import java.util.TreeMap;
public class ProductAndConsumer {
List<Object> container = new LinkedList<>();
public static void main(String[] args) {
ProductAndConsumer productAndConsumer = new ProductAndConsumer();
Thread thread1 = new Thread(new Product(productAndConsumer));
Thread thread2 = new Thread(new Consumer(productAndConsumer));
thread1.start();
thread2.start();
}
public synchronized void addObject() throws InterruptedException {
if (container.size() >=100){
wait();
}
container.add(new Object());
System.out.println("正在出产第"+ container.size() + "个");
notify();
}
public synchronized void conObject() throws InterruptedException {
if (container.size() == 0){
wait();
}
container.remove(0);
System.out.println("正在消费第"+ container.size() + "个");
notify();
}
}
class Product implements Runnable{
private final ProductAndConsumer productAndConsumer;
Product(ProductAndConsumer productAndConsumer){
this.productAndConsumer = productAndConsumer;
}
@Override
public void run() {
while (true){
try {
productAndConsumer.addObject();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable{
private final ProductAndConsumer productAndConsumer;
Consumer(ProductAndConsumer productAndConsumer){
this.productAndConsumer = productAndConsumer;
}
@Override
public void run() {
while (true){
try {
productAndConsumer.conObject();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
解说
- 运用类
ProductAndConsumer
作为锁目标,container作为容器 - wait notify来进行线程之间的通讯,在数量满足条件时运用wait开释当时锁目标,另一个目标拿到锁之后进行出产或消费
图示
sleep()
Thread.sleep()
是Java中一个静态native办法,用于使当时线程进入休眠状况(暂停履行)一段指定的时刻。
它的办法签名为:
public static native void sleep(long millis) throws InterruptedException;
参数millis
表明线程休眠的时刻,以毫秒为单位。传入的值是一个正整数,表明线程要休眠的毫秒数。注意,该办法会抛出InterruptedException
反常,由于线程在休眠期间或许被其他线程中止。
特点和用法
-
线程堵塞:调用
Thread.sleep()
办法会导致当时线程暂停履行,进入堵塞状况。在指定的时刻内,线程不会进行任何操作。 - 时刻精度:传入的休眠时刻是以毫秒为单位,但实际的休眠时刻或许会稍长或稍短。详细的精度取决于底层操作系统和JVM的完成。
-
中止呼应:假如在线程休眠期间,另一个线程中止了正在休眠的线程,
Thread.sleep()
办法会抛出InterruptedException
反常。能够在catch
块中处理该反常,或许将反常持续向上抛出。 -
不会开释锁:
Thread.sleep()
办法会暂停当时线程的履行,但不会开释任何锁。假如线程在履行同步代码块或同步办法时调用了Thread.sleep()
,其他线程仍无法取得该锁。 -
静态办法:
Thread.sleep()
是一个静态办法,能够直接经过Thread
类调用,无需创建线程目标。 - 用处:常见的用处包含模拟推迟、定时任务、操控线程履行次序等。
Thread.sleep
和TimeUnit
比较
-
精度和可读性:
Thread.sleep()
的参数是以毫秒为单位的时刻值,表明线程要休眠的时刻。而TimeUnit
供给了更高层次的时刻单位,如TimeUnit.SECONDS
表明秒,TimeUnit.MILLISECONDS
表明毫秒等。运用TimeUnit
能够使代码更具可读性,而不需求手动计算毫秒数。 -
反常处理:
Thread.sleep()
办法会抛出InterruptedException
反常,由于线程在休眠期间或许会被其他线程中止。而TimeUnit
方式不会直接抛出反常,需求开发者手动处理中止情况。 -
静态与非静态:
Thread.sleep()
是Thread
类的静态办法,能够直接经过类名调用。而TimeUnit
是一个枚举类,需求经过详细的枚举常量来调用其办法,例如TimeUnit.SECONDS.sleep(1)
。 -
可读性和易用性: 运用
TimeUnit
能够进步代码的可读性,由于能够直观地表明时刻单位。此外,TimeUnit
还供给了其他办法,如TimeUnit.toMillis()
、TimeUnit.toSeconds()
等,便利进行时刻单位之间的转换。
TimeUnit
源码
以下是截取部分源码
public enum TimeUnit {
/**
* Performs a {@link Thread#sleep(long, int) Thread.sleep} using
* this time unit.
* This is a convenience method that converts time arguments into the
* form required by the {@code Thread.sleep} method.
*
* @param timeout the minimum time to sleep. If less than
* or equal to zero, do not sleep at all.
* @throws InterruptedException if interrupted while sleeping
*/
public void sleep(long timeout) throws InterruptedException {
if (timeout > 0) {
long ms = toMillis(timeout);
int ns = excessNanos(timeout, ms);
Thread.sleep(ms, ns);
}
}
}
能够看到TimeUnit
底层还是调用了Thread.sleep()
有一个比较隐含的地便利是当运用过TimeUnit
的sleep
办法时,假如传入的时刻小所以不会进入if判别,而Thread.sleep()
办法假如传参小于0则会抛出反常(源码见下)
public static void sleep(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
sleep(millis);
}
join()
Thread.join()
是Java中的一个办法,用于等候调用该办法的线程履行结束。它的作用是让当时线程等候指定线程履行结束,然后再持续履行当时线程的后续代码。
简单来说便是堵塞主线程。
简单示例
package ThreadMethod;
public class JoinMethod {
public static void main(String[] args) {
Thread thread1 = new Thread(()->{
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程履行结束");
},"子线程");
thread1.start();
try {
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程履行结束");
}
}
成果
子线程履行结束
主线程履行结束
能够看到子线程等候了3秒,但是终究还是子线程先履行结束在履行主线程打印,原因是由于thread1.join 对主线程进行了堵塞,这是主线程需求等子线程履行结束才会履行后面的语句
特点和注意事项
-
等候履行: 调用
join()
办法的线程将会等候指定线程履行结束。假如指定线程已经履行结束,则join()
办法会立即返回。 -
堵塞调用线程: 在调用
join()
办法期间,当时线程将会被堵塞,暂停履行。只有当指定线程履行结束后,当时线程才会解除堵塞,持续履行。 -
反常处理:
join()
办法会抛出InterruptedException
反常,由于在等候过程中,当时线程或许会被中止。能够在catch
块中处理该反常,或将反常持续向上抛出。 -
次序履行: 经过运用
join()
办法,能够操控线程的履行次序。调用join()
办法后,当时线程会等候指定线程履行结束,然后再持续履行后续代码。 -
调用目标:
join()
办法是一个实例办法,需求经过线程目标调用。例如,假如thread1
是一个Thread
目标,能够运用thread1.join()
来等候thread1
履行结束。
源码和底层完成
以下截取部分Thread.join
源码
// 入口
public final void join() throws InterruptedException {
join(0);
}
// 调用的join(0)
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
// 实际调用的本地native办法 wait
public final native void wait(long timeout) throws InterruptedException;
能够看到join的底层还是运用wait办法完成的,子线程调用wait办法让主线程进入等候状况,在运行结束后自动调用notify办法唤醒主线程。(详细唤醒的办法在jvm中)
有一点疑问我没有找到解说:为什么调用子线程的wait(0)办法,堵塞的确是主线程呢?
CountDownLatch
和CyclicBarrier
运用countDownLatch
和CyclicBarrier
也能够完成线程之间的堵塞,详细暂不讨论
yeild() (让步)
yield()
是一个静态办法,它属于 Thread
类,用于提示调度器将当时线程让出 CPU 的履行权,使得其他具有相同优先级的线程有时机履行。
yield()
办法的调用并不能确保一定会使其他线程取得履行时机,它仅是一个提示。详细的调度行为取决于操作系统和 JVM 的完成。因此,在实际应用中,不该过度依赖 yield()
办法来操控线程的履行次序,而应运用更牢靠的线程同步机制来完成需求的线程协作和同步。