当前位置: 首页 > article >正文

多线程基础系列-多线程初识

文章目录

  • 多线程基础系列-多线程初识
    • 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问!


http://www.kler.cn/a/503801.html

相关文章:

  • 金融项目实战 02|接口测试分析、设计以及实现
  • 日志系统实践
  • Redis是单线程还是多线程?
  • 如何开放2375和2376端口供Docker daemon监听
  • 详解 Docker 启动 Windows 容器第二篇:技术原理与未来发展方向
  • C++内存泄露排查
  • kafka原理和实践
  • Linux:进程概念(二.查看进程、父进程与子进程、进程状态详解)
  • vscode的安装与使用
  • docker简单使用
  • 爬山算法与模拟退火算法的全方面比较
  • EDM 电子邮件自动化营销的关键步骤 —— 邮箱地址验证
  • C#实现条形码识别
  • 高录用快检索/JPCS独立出版-第六届新材料与清洁能源国际学术会议(ICAMCE 2025)
  • 国产编辑器EverEdit - 扩展脚本:新建同类型文件(避免编程学习者反复新建保存练习文件)
  • BUUCTF:misc刷题记录4(会持续更新的)
  • leetcode79.单词搜索
  • C# 数据拟合教程:使用 Math.NET Numerics 的简单实现
  • 图像处理|开运算
  • 进程同步之信号量机制
  • OJ12:160. 相交链表
  • LangGraph 教程:初学者综合指南(1)
  • Android string.xml中特殊字符转义
  • 项目概述、开发环境搭建(day01)
  • 【Flink】Flink内存管理
  • Word中设计好标题样式后不显示