JAVA多线程基础篇 1、线程概念与线程生命周期

本文探讨了多线程编程的重要性,包括进程与线程的区别,线程生命周期、状态及其管理方法。深入剖析了并发编程挑战如可见性、有序性和原子性,以及如何通过synchronized、volatile和Condition类实现线程安全。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 掌握多线程编程的重要性

  • 理解了多线程,才能理解了现代分时操作系统,并进一步理解更多系统应用组件的底层原理。

  • 并发编程是低级程序员跨越到中高级程序员必过一关,是区分程序员水平的最常用的标准。在程序员求职过程中,多线程知识会被频繁问及考察,几乎所有的招聘要求里都会要求熟悉高并发编程。

2. 为何使用线程

在计算机早期并没有多线程的概念,程序在纸带上编码送入计算机运行,但是由于计算机当时是非常昂贵的,如何提高计算机CPU利用率这一问题提出了。
这一问题的提出,引发了一系列技术的进展,计算机处理任务的一步步演化。
单进程=>多进程批处理=>多进程并行处理=>多线程处理=>协程(纤程)
使用线程后,同一个程序可以创建多个任务,进一步压榨CPU能力。

3.进程、线程

  • 进程是操作系统进行资源分配的基本单位。
    当计算机运行可执行文件时,计算机开辟内存空间、分配文件描述符以及端口等一系列资源。进程是独立拥有这些资源的基本单位。注意,一个程序可以创建多个进程,比如我们同时可以在电脑上打开多个浏览器窗口或者登录多个QQ。所以说,进程是静态资源的概念。

  • 线程是操作系统进行资源调度的基本单位。
    所有的程序的运行都是以线程为单位来执行的。一个程序在运行的时候,至少包含一个线程,即主线程。如果一个程序运行过程中,有多个“执行路径”,即是多线程程序。线程运行过程中,由于CPU轮转等原因,会发生线程切换。所以说,线程是动态调度的概念。

    单CPU机器有必要用多线程吗?
    多线程能够合理利用CPU和IO资源,有利于提高程序性能。

    线程数是否越多越好?
    线程切换会消耗资源,因此不是越多越好。

    线程数设置多少合适?
    以CPU核数和实际压测结果定,估算线程数需要综合考虑CPU核数、CPU利用率、程序的响应时间。

4. 多线程编程的复杂性

在计算机体系的发展过程中,CPU、内存、IO速度有非常大的差别。
根据估算CPU速度是内存读取速度的1000倍以上。,内存是磁盘IO或网络IO的1000倍以上。

  • 为了解决CPU与内存速度适配的问题,增加了缓存的设计。
  • 为了优化使用缓存,编译程序会优化指令的执行次序。
  • 为了解决CPU与IO速度的差异,增加了线程的设计,以分时复用CPU

这就导致了并发编程需要注意的三个问题。

  • 可见性问题 (剧透,可使用volatile synchronized 解决)
  • 有序性问题 (剧透,可使用volatile synchronized 解决)
  • 原子性问题 (剧透,可synchronized 解决)

5. 线程的生命周期

线程状态

6. 线程的状态

  1. NEW : 线程刚刚创建,还没有启动。
  2. RUNNABLE :可运行状态,分为READY和RUNNING。均是可运行状态。当Thread.yield()时,线程主动礼让。
  3. WAITING:等待被唤醒。
  4. TIMED WAITING:隔一段时间后自动唤。
  5. BLOCKED:被阻塞,正在等待锁。
  6. TERMINATED:线程结束。

6.1 实验观察线程状态(一)

  • 观察NEW、RUNNABLE、TERMINATED状态
public class ThreadStateMonitor1 {
    public static void main(String[] args) throws InterruptedException {
        /*
        观察 NEW RUNNABLE TERMINATED 状态
        * */
        Thread t1 = new Thread(()->{
            for(int i=0;i<3;i++) {
                System.out.print(Thread.currentThread().getState());
                System.out.println("do something " + i);
            }
        });
        System.out.println(t1.getState());
        t1.start();
        //join 方法在此处表示,main线程等待t1完成线程方法。
        t1.join();
        System.out.println(t1.getState());
    }
}

运行结果:

线程状态1

6.2 实验观察线程状态(二)

  • t1线程start后,就立即拿到锁。
  • 接着,o.wait() 就会释放锁,并进入到WAITING状态。如果o.wait(1000),会进入TIMED WAITING状态。
  • MAIN方法的主线程接着拿到锁,并叫醒t1,t1醒后就开始排队争抢锁,进入BLOCKED状态。
  • 最后t1拿到了锁,记者开始执行wait()之后的命令,打印"我wait好了。我执行到这里的时候,表示又获得了锁"
  • 最后main等待t1一起“退休”,进入TERMINATED状态。
public class ThreadStateMonitor2 {
    public static void main(String[] args) throws InterruptedException {
        //o是一个锁对象
        Object o = new Object();
        //让t1线程获得o的锁后,就等待被唤醒
        Thread t1 = new Thread(()->{
            synchronized (o) {
                System.out.println("我获得了锁");
                try {
                    //o.wait(1000);
                    o.wait();
                    System.out.println("我wait好了。我执行到这里的时候,表示又获得了锁");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();
        //MAIN线程让t1先执行一会
        Thread.sleep(100);
        //这个时候t1已经wait了
        System.out.println(t1.getState());
        synchronized (o){
            o.notify();
            System.out.println(t1.getState());
        }
        Thread.sleep(100);
        //t1
        t1.join();
        System.out.println(t1.getState());
    }
}

运行结果:
线程状态2

7. 线程的常用方法

7.1 Thread类上的方法

作用在线程上,由线程对象执行。

  • Thread.start(),通过 Thread 调用 start() 方法来启动线程。在Thread内部以静态代理的方式运行Runnable对象的run的方法。
  • Thread.sleep(millisec),是Thread类的静态方法,会休眠当前正在执行的线程,millisec 单位为毫秒。sleep()不会释放当前拿到的锁。
  • Thread.join(),在线程A调用线程B的 join() 方法后,A会直到B线程结束才会执行后续指令。
  • Thread.yield(),线程礼让,建议线程调度器调度其他线程运行,最终礼让结果由操作系统实现。

7.2 Object类上的方法

作用在锁对象上。

  • Object.wait(),调用 wait() 使得线程进入等待被唤醒的队列,wait后会释放锁资源。等待被唤醒。
    这是因为,如果没有释放锁,那么其它线程就无法进入对象的同步方法或者同步控制块中,那么就无法执行 notify() 或者 notifyAll() 来唤醒挂起的线程,造成死锁。
  • Object.notify() 唤醒在Object上的等待队列的某一个线程,由操作系统决定。
  • Object.notifyAll() 唤醒在Object上的等待队列所有线程。

7.3 Condition类上的方法

java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。相比于 wait() 这种等待方式,await() 可以指定等待的条件,因此更加灵活。

8 线程安全

线程安全指的是程序在多线程环境里的执行,是符合预期的。要实现线程安全首先需要考虑竞态条件。对于无状态的线程,是绝对安全的。对于共享状态的线程,需要考虑可见性、指令顺序性和原子性等多个情况。因为多线程的执行是复杂的、但是JAVA多线程的原理和技术并不是难以掌握的。记住,并不比掌握手动挡的档位复杂。

总结

线程是cpu调度的基本单位,线程的生命周期中会经过多种状态的变化,就像人生,有生有死、有争有夺、有等待、有迷惘。也有其忙碌、专注的时刻。

多线程系列在github上有一个开源项目,主要是本系列博客的实验代码。

https://github.com/forestnlp/concurrentlab

如果您对软件开发、机器学习、深度学习有兴趣请关注本博客,将持续推出Java、软件架构、深度学习相关专栏。

您的支持是对我最大的鼓励。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悟空学编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值