多线程基础系列-多线程初识
文章目录
- 多线程基础系列-多线程初识
- 1. 何为多线程?
- 1.1 线程
- 1.2 进程
- 2. 线程的生命周期
- 2.1 线程的六种状态
- 2.1.1 新建(New)
- 2.1.2 就绪(Runnable)
- 2.1.3 运行(Running)
- 2.1.4 阻塞(Blocked)
- 2.1.5 等待(Waiting)
- 2.1.6 终止(Terminated)
- 2.2 阻塞和等待的区别
- 2.3 wait(),notify()和suspend(),resume()之间的区别
- 2.3 线程使用示例
- 3. 创建线程的几种方式
- 3.1 定义Thread类的子类,并重写该类的run方法
- 3.2 定义Runnable接口的实现类,并重写该接口的run()方法
- 3.3 定义Callable接口的实现类,并重写该接口的call()方法
- 3.4 线程池的方式
- 4. 参考和感谢
多线程基础系列-多线程初识
1. 何为多线程?
1.1 线程
线程是操作系统能够进行调度的最小单位,是程序执行流的最小单元,它允许程序同时执行多个任务。
1.2 进程
一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用。而线程是在
进程中执行的一个任务。
线程和进程的区别:
- 进程是运行中的应用程序,线程是进程的内部的一个执行序列
- 进程是资源分配的最小单位,线程是CPU调度的最小单位。
- 一个进程可以有多个线程。线程又叫做轻量级进程,多个线程共享进程的资源
2. 线程的生命周期
2.1 线程的六种状态
2.1.1 新建(New)
当创建一个Thread
对象后,线程就处于新建状态。此时,线程还没有开始执行,还没有被调度到CPU上运行。
Thread thread = new Thread(() -> {
// 线程任务
});
2.1.2 就绪(Runnable)
当调用Thread
对象的start()
方法后,线程进入就绪状态。此时,线程已经准备好运行,但还没有被调度到CPU上。线程调度器会根据线程的优先级等因素选择一个线程来运行。
thread.start();
2.1.3 运行(Running)
当线程被调度到CPU上执行时,线程进入运行状态。此时,线程正在执行其run()
方法中的代码。
2.1.4 阻塞(Blocked)
线程在运行过程中可能会因为某些原因或者获取锁失败而被阻塞,进入阻塞状态,进入阻塞状态是被动的。常见的阻塞原因包括:
- 等待锁:线程尝试获取一个已经被其他线程持有的锁。
- 等待I/O:线程执行I/O操作,如读写文件或网络通信。
- 等待通知:线程调用
wait()
方法等待其他线程的通知。
2.1.5 等待(Waiting)
-
无限等待:线程调用
wait()
、join()
或LockSupport.park()
等方法时,会进入等待状态。此时,线程不会被调度,直到被唤醒或中断 -
超时等待(Timed Waiting)
线程调用wait(long timeout)
、sleep(long millis)
或LockSupport.parkNanos(long nanos)
等方法时,会进入超时等待状态。此时,线程在指定的时间内不会被调度,直到超时或被唤醒。
2.1.6 终止(Terminated)
当线程的run()
方法执行完毕或因为异常退出时,线程进入终止状态。此时,线程已经完成其任务,不再可运行。
2.2 阻塞和等待的区别
阻塞状态与等待状态的区别:
实际上两者不用刻意区分两者,因为两者都会暂停线程的执行。
两者的区别是:进入等待状态是线程主动的,而进入阻塞状态是被动的。更进一步的说,进入阻塞状态是在同步, 而进入等待状态是在同步代码之内。
两者的共同点是:
都暂时停止线程的执行,线程本身不会占用CPU时间片。
区别是调用了sleep方法的线程直接受CPU调度,而wait则是等待另外的java线程在持有同一个对象锁的同步块,方法中进行notify调用。
2.3 wait(),notify()和suspend(),resume()之间的区别
-
wait()方法使得线程进入阻塞等待状态,并且释放锁
-
notify()唤醒一个处于等待状态的线程,它一般跟wait()方法配套使用。
-
suspend()使得线程进入阻塞状态,并且不会自动恢复,必须对应的resume()被调用,才能使得线程重新进入可执行状态。suspend()方法很容易引起死锁问题。
-
resume()方法跟suspend()方法配套使用。
-
suspend()不建议使用,因为suspend()方法在调用后,线程不会释放已经占有的资 源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。
-
虽然 suspend() 和 resume() 方法在早期版本的Java中被使用,但由于它们可能导致死锁和其他线程安全问题,已经被废弃。推荐使用 wait() 和 notify() / notifyAll() 方法来实现线程的暂停和恢复,这些方法更加安全和可靠
2.3 线程使用示例
package com.xmc.tmp;
/**
* Created by xmc on 2025/1/13.
*/
public class ThreadTest {
private static Object object = new Object();
public static void main(String[] args) throws Exception {
Thread thread1 = new Thread(() -> {
try {
for(int i = 0; i< 1000; i++){
System.out.print("");
}
Thread.sleep(500);
System.out.println("thread1睡眠时间到了。。。");
synchronized (object){
object.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread thread2 = new Thread(() -> {
try {
synchronized (object){
Thread.sleep(1000);
}
Thread.sleep(1000);
synchronized (object){
object.notify();
System.out.println("唤醒了thread1。。。");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("1-"+thread1.getState());
thread1.start();
thread2.start();
System.out.println("2-"+thread1.getState());
while (thread1.isAlive()){
System.out.println("---"+thread1.getState());
Thread.sleep(100);
}
System.out.println("3-"+thread1.getState());
}
}
执行结果如下:
1-NEW
2-RUNNABLE
---RUNNABLE
---TIMED_WAITING
---TIMED_WAITING
---TIMED_WAITING
---TIMED_WAITING
thread1睡眠时间到了。。。
---BLOCKED
---BLOCKED
---BLOCKED
---BLOCKED
---BLOCKED
---WAITING
---WAITING
---WAITING
---WAITING
---WAITING
---WAITING
---WAITING
---WAITING
---WAITING
唤醒了thread1。。。
3-TERMINATED
3. 创建线程的几种方式
Java中创建线程主要有以下这几种方式:
- 定义Thread类的子类,并重写该类的run方法
- 定义Runnable接口的实现类,并重写该接口的run()方法
- 定义Callable接口的实现类,并重写该接口的call()方法,一般配合Future使用
- 线程池的方式
线程的创建方式主要有以下几种:
3.1 定义Thread类的子类,并重写该类的run方法
public class ThreadTest {
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("do something...");
}
}
3.2 定义Runnable接口的实现类,并重写该接口的run()方法
实现Runnable接口的线程,没有返回值,本质上还是Thead线程。
public class ThreadTest {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("do something...");
}
}
3.3 定义Callable接口的实现类,并重写该接口的call()方法
实现Callable接口的线程,有返回值,经常和FutureTask搭配使用,本质上还是Thead线程。
public class ThreadTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThreadCallable mc = new MyThreadCallable();
FutureTask<Integer> ft = new FutureTask<>(mc);
Thread thread = new Thread(ft);
thread.start();
System.out.println(ft.get());
}
}
class MyThreadCallable implements Callable {
@Override
public String call()throws Exception {
return "do something...";
}
}
3.4 线程池的方式
public class ThreadTest {
public static void main(String[] args) throws Exception {
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 1,
TimeUnit.MINUTES, new ArrayBlockingQueue<Runnable>(20), new CustomizableThreadFactory("task-Thread-pool"));
executor.execute(() -> {
System.out.println("do something...");
});
//关闭线程池
executor.shutdown();
}
}
4. 参考和感谢
两万字!多线程50问!