多线程系列:周期和状态

前言

随着计算机硬件性能的不断提升,单核CPU已经不再能够满足人们的需求,多核CPU已经成为主流。在多核CPU上,线程的使用越来越重要,可以充分发挥计算机的性能。Java语言是一种面向对象的编程语言,线程是Java语言中非常重要的概念之一。本文将围绕线程生命周期和线程状态,从不同角度讲解线程的相关知识。

小故事

假设你是一个电商平台的后台开发工程师,你们平台上有很多商家和买家。商家们可以在平台上发布商品,买家们可以在平台上浏览和购买商品。现在,你们平台上的访问量越来越大,很多用户反映访问速度变慢,你们需要优化系统。

你仔细观察了一下系统,发现其中一个瓶颈在于商品列表的生成速度。当一个用户在浏览商品列表时,系统需要从数据库中查询商品信息,然后将这些信息渲染成页面。这个过程比较耗时,如果在一个请求中处理,会导致其他用户的请求被阻塞,系统响应变慢。

为了解决这个问题,你们决定采用多线程技术。具体地,当一个用户请求商品列表时,系统会开启一个新的线程,这个线程负责查询数据库,然后将结果返回给主线程。主线程在等待子线程返回结果的同时,可以处理其他用户的请求,这样就避免了请求阻塞的问题。

假设有这么一天,你收到了一个名为小明的用户反馈,说他浏览商品时遇到了问题。他点开商品列表页面,发现页面上的商品信息不断闪烁,看不清楚,很不友好。你们经过了一番调查发现,这是由于多线程技术带来的问题。具体地,当小明请求商品列表时,子线程查询数据库的速度比较慢,返回结果时,主线程正在处理其他请求,页面已经渲染出来了。这时子线程返回的结果更新了页面的数据,导致页面上的商品信息不停地闪烁。

为了解决这个问题,你们需要对多线程进行优化。具体地,你们可以引入线程池技术。线程池维护一个线程队列,每次有请求时,从线程池中取出一个空闲线程去处理请求,处理完毕后,线程不销毁,而是放回线程池中。这样就可以避免线程频繁的创建和销毁,提高系统的性能。同时,你们还可以控制线程池的大小,避免线程数量过多,导致系统的负荷过大。

通过引入线程池技术,你们成功地优化了系统,提高了系统的性能,用户体验得到了很大的改善。

线程生命周期

线程的生命周期可以分为五个阶段:

  1. NEW
  2. RUNNABLE
  3. BLOCKED
  4. WAITING
  5. TERMINATED

NEW

线程处于新建状态时,尚未调用 start() 方法。此时线程还没有被分配系统资源,如CPU和内存等。

示例代码:

public class ThreadStateDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("线程开始执行");
        });
        System.out.println("线程状态:" + thread.getState());
    }
}
复制代码

RUNNABLE

线程处于就绪状态时,等待系统分配CPU时间片。此时线程已经被分配了系统资源,可以运行。

示例代码:

public class ThreadStateDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("线程开始执行");
        });
        System.out.println("线程状态:" + thread.getState());
        thread.start();
        System.out.println("线程状态:" + thread.getState());
    }
}
复制代码

BLOCKED

线程处于阻塞状态时,等待获取一个监视器锁(synchronized关键字)。当线程获取到锁时,状态将重新进入就绪状态。

示例代码:

public class ThreadStateDemo {
    public static void main(String[] args) {
        Object lock = new Object();
        Thread thread1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程1获取到锁");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread thread2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程2获取到锁");
            }
        });
        thread1.start();
        thread2.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程1状态:" + thread1.getState());
        System.out.println("线程2状态:" + thread2.getState());
    }
}
复制代码

WAITING

线程处于等待状态时,等待某个条件的满足,例如等待线程的结束或者等待输入输出等。当条件满足时,状态将重新进入就绪状态。

示例代码:

public class ThreadStateDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("线程开始执行");
            try {     Thread.sleep(5000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});
thread.start();
try {
    thread.join();
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println("线程状态:" + thread.getState());
}

复制代码

TERMINATED

线程处于终止状态时,表示线程已经执行完成,不再具有运行的状态。

示例代码:

public class ThreadStateDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("线程开始执行");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程状态:" + thread.getState());
    }
}
复制代码

线程状态转换

线程的状态转换可以通过如下图示表示:

NEW

RUNNABLE

BLOCKED

WAITING

TERMINATED

图中,线程状态之间的转换是有条件的,可以通过一些API方法来实现。

sleep()

sleep() 方法可以让线程进入 TIMED_WAITING 状态,在一定时间后自动转换为就绪状态。 sleep() 方法会释放当前线程的CPU资源,但不会释放当前线程持有的锁资源。

示例代码:

public class ThreadStateDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("线程开始执行");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread.start();
        System.out.println("线程状态:" + thread.getState());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程状态:" + thread.getState());
    }
}
复制代码

wait()

wait() 方法可以让线程进入 WAITING 状态,等待其他线程的通知,通常与 notify()notifyAll() 方法配合使用。调用 wait() 方法时,线程必须持有对象的锁,否则会抛出 IllegalMonitorStateException 异常。

示例代码:

public class ThreadStateDemo {
    public static void main(String[] args) {
        Object lock = new Object();
        Thread thread1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程1获取到锁");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程1继续执行");
            }
        });
        Thread thread2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程2获取到锁");
                lock.notify();
            }
        });
        thread1.start();
        thread2.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System .println("线程1状态:" + thread1.getState());
        }
复制代码

join()

join() 方法可以让一个线程等待另一个线程的结束,主线程等待子线程结束的例子在前面已经介绍过了。调用 join() 方法时,线程必须处于 RUNNABLE 状态。

示例代码:

public class ThreadStateDemo {
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            System.out.println("线程1开始执行");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程1执行完成");
        });
        Thread thread2 = new Thread(() -> {
            System.out.println("线程2开始执行");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程2执行完成");
        });
        thread1.start();
        thread2.start();
        try {
            thread1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程1状态:" + thread1.getState());
        System.out.println("线程2状态:" + thread2.getState());
    }
}
复制代码

synchronized

synchronized 关键字用于同步代码块或同步方法,可以保证多个线程访问同一对象时,不会出现数据不一致的情况。

当一个线程获取对象锁后,其他线程访问同步代码块或同步方法时会被阻塞,直到锁被释放。

示例代码:

public class ThreadStateDemo {
    private int num = 0;
    public synchronized void add() {
        num++;
    }
    public static void main(String[] args) {
        ThreadStateDemo demo = new ThreadStateDemo();
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                demo.add();
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                demo.add();
            }
        });
        thread1.start();
        thread2.start();
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("num = " + demo.num);
    }
}
复制代码

线程生命周期

线程的生命周期从创建开始,直到线程终止。线程生命周期可以分为五个阶段:

  • 新建状态(New)
  • 就绪状态(Runnable)
  • 运行状态(Running)
  • 阻塞状态(Blocked)
  • 终止状态(Terminated)

如下图所示:

NEW

RUNNABLE

RUNNING

BLOCKED

WAITING

TIMED_WAITING

TERMINATED

图中,不同状态之间的转换如下:

  • 新建状态(New):线程已经被创建,但还没有调用

start() 方法,处于这个状态的线程并没有被执行。

  • 就绪状态(Runnable):线程已经被调度,但还没有开始执行。

  • 运行状态(Running):线程正在执行中。

  • 阻塞状态(Blocked):线程因为某些原因被阻塞了,无法继续执行。阻塞状态包括以下几种:

    • 等待阻塞(WAITING):线程调用了 wait() 方法,进入等待状态。
    • 同步阻塞(BLOCKED):线程在等待获取锁。
    • 其他阻塞(TIMED_WAITING):线程调用了 sleep()join()park() 等方法,进入等待状态。
  • 终止状态(Terminated):线程执行完毕或者因为异常退出,进入终止状态。

线程的状态可以通过 Thread 类中的 getState() 方法获取。示例代码:

public class ThreadStateDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("线程开始执行");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程执行完成");
        });
        System.out.println("线程状态:" + thread.getState());
        thread.start();
        System.out.println("线程状态:" + thread.getState());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程状态:" + thread.getState());
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程状态:" + thread.getState());
    }
}
复制代码

输出结果如下:

线程状态:NEW
线程状态:RUNNABLE
线程状态:RUNNING
线程状态:TERMINATED
复制代码

面试问题

  1. 什么是线程生命周期?

线程生命周期是指线程从创建到销毁的整个过程,包括线程的状态转换以及对应的操作。

  1. 线程有哪几种状态?
  • NEW:线程已经被创建,但是还没有开始执行。
  • RUNNABLE:线程已经准备好运行,但是可能还没有获得 CPU 资源。
  • RUNNING:线程正在运行中。
  • BLOCKED:线程被阻塞了,无法继续执行。
  • WAITING:线程在等待某个条件的出现。
  • TIMED_WAITING:线程在等待一段时间后自动唤醒。
  • TERMINATED:线程执行完成或者异常终止。
  1. 如何创建线程? 答:可以通过继承 Thread 类或者实现 Runnable 接口来创建线程。例如:
// 继承 Thread 类
public class MyThread extends Thread {
    @Override
    public void run() {
        // 线程执行的代码
    }
}

// 实现 Runnable 接口
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 线程执行的代码
    }
}

// 创建线程并启动
MyThread thread1 = new MyThread();
thread1.start();

MyRunnable runnable = new MyRunnable();
Thread thread2 = new Thread(runnable);
thread2.start();
复制代码
  1. 如何启动线程?

可以通过调用线程对象的 start() 方法来启动线程,例如:

MyThread thread = new MyThread();
thread.start();
复制代码
  1. 如何停止线程?

可以通过调用线程的 interrupt() 方法来请求线程停止执行,并在线程中判断该方法是否被调用。例如:

public class MyThread extends Thread {
    @Override
    public void run() {
        while (!Thread.interrupted()) {
            // 线程执行的代码
        }
    }
}

// 停止线程
MyThread thread = new MyThread();
thread.start();
thread.interrupt();
复制代码
  1. 如何等待线程执行完成?

可以调用线程对象的 join() 方法来等待线程执行完成,例如:

MyThread thread = new MyThread();
thread.start();
thread.join();
复制代码
  1. 如何使线程进入阻塞状态

可以调用线程的 sleep() 方法或者在 synchronized 块中调用 wait() 方法来使线程进入阻塞状态,例如:

// sleep() 方法
public class MyThread extends Thread {
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

// wait() 方法
public class MyObject {
    public synchronized void method() {
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
复制代码
  1. 如何使线程重新进入就绪状态

答:可以通过调用线程对象的 interrupt() 方法或者在 synchronized 块中调用 notify() 或 notifyAll() 方法来使线程重新进入就绪状态,例如:

// interrupt() 方法
public class MyThread extends Thread {
    @Override
    public void run() {
        while (!Thread.interrupted()) {
            // 线程执行的代码
        }
    }
}

// notify() 方法
public class MyObject {
    public synchronized void method() {
        notify();
    }
}
复制代码
  1. 如何判断线程是否在运行中? 答:可以通过判断线程对象的状态来判断线程是否在运行中,例如:
Thread.State state = thread.getState();
if (state == Thread.State.RUNNING) {
    // 线程正在运行中
} else {
    // 线程不在运行中
}
复制代码
  1. 如何处理线程运行过程中的异常? 答:可以通过 try-catch 块来捕获线程执行过程中的异常,并进行相应的处理,例如:
public class MyThread extends Thread {
    @Override
    public void run() {
        try {
            // 线程执行的代码
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
复制代码

总结

本文介绍了线程的生命周期和状态,以及线程同步的相关内容。线程是多任务处理的基本单位,掌握线程的生命周期和状态对于编写高效的多线程程序至关重要。在编写多线程程序时,需要注意线程同步的问题,避免数据不一致的情况。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值