线程(Thread)

简介: 🌼什么是线程🌼Java 线程在代码中的体现🌷线程对象🌷在 Java 代码中创建线程🌷启动线程🌷代码演示创建线程🌼多线程下各个线程之间执行先后的随机性🌷什么情况下,子线程会被先执行🌷什么情况下,会出现线程调度🌼线程安全🌷线程之间的数据共享🌷演示什么是线程不安全🌷线程不安全的原因🌷原子性🌷系统角度分析线程不安全的原因

🌼什么是线程

线程属于进程之下,一个进程下可以有多个线程,一开始就存在的线程被称为主线程

主线程和其他线程之间地位是完全相等的,没有任何特殊性

线程是 OS(操作系统)进行调度(分配 CPU)基本单位


🌼Java 线程在代码中的体现

🌷线程对象

Java 是一个面向对象的语言,线程在 Java 中也是以对象的形式体现的 —— java.lang.Thread 类(包括其子类)的一个对象


🌷在 Java 代码中创建线程

通过继承 Thread 类,并且重写 run 方法,实例化 Thread 对象,再进一步使用线程对象中的一些方法来操作对象


🌷启动线程

当手中有一个 Thread 对象时,调用其 start() 方法来启动线程

注意:

  1. 一个已经调用过 start() 的对象不能再调用 starrt() ,再调用就会有异常
  2. 调用 start() 方法时千万不能错调用成 run() 方法


🌷代码演示创建线程


public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("我是子线程");
    }
    public static void main(String[] args) {
        MyThread m = new MyThread();
        m.start();
        System.out.println("我是主线程");
    }
}

🌼多线程下各个线程之间执行先后的随机性

start() 是在主线程中的语句,说明 start() 执行后,主线程正在 CPU 上,所以大概率是start() 的下一条语句先执行,但实际运行下,各个线程的执行先后都是不确定的


🌷什么情况下,子线程会被先执行

start() 执行完之后,非常碰巧的发生了一次线程调度,此刻主线程将不再持有 CPU,在调度之后,其他子线程持有 CPU 就会执行子线程中的语句


🌷什么情况下,会出现线程调度

  1. CPU 空闲
    1.1 当前运行着的 CPU 执行结束了
    1.2 外部条件将线程阻塞
    1.3 线程主动放弃 CPU
  2. 被调度器主动调度
    2.1 高优先级线程抢占
    2.2 时间片耗尽(这种情况最常见)


🌼线程安全

🌷线程之间的数据共享

  1. 线程之间共享的内存区域
    堆区、方法区、运行时常量池区
  2. 线程之间私有的区域
    pc寄存器、栈

正是因为线程之间有数据共享,才会出现线程线程的不安全情况


🌷演示什么是线程不安全

public class Main {
    static int r = 0;
    static final int COUNT = 1_0000;
    static class Add extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < COUNT; i++) {
                r++;
            }
        }
    }
    static class Sub extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < COUNT; i++) {
                r--;
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Add add = new Add();
        add.start();
        Sub sub = new Sub();
        sub.start();
        add.join(); // 等待 add 线程执行结束
        sub.join();
        System.out.println(r);
    }
}

这段代码本来的运行结果应该是 “ 0 ”,但是实际的运行结果却相差甚远,且每次的运行结果都是随机的


🌷线程不安全的原因

  1. 多个线程之间操作同一块数据(共享数据)
  2. 至少有一个线程在修改这块共享内存


由于线程调度的原因,一个线程在刚读取到共享内存且还没修改时可能被 CPU 调度,然后另一个线程再读取这块内存进行修改,修改完之后,上一个线程再回到原来的状态,但因为之前的状态共享内存的值还没被修改,就会导致同一个值被多次操作,从而导致最终的值出现巨大偏差


🌷原子性

程序员的预期是 r++ 或者 r-- 是一个原子性的操作(全部完成或全没完成),但实际执行起来,保证不了原子性,所以会出错

原子性被破坏是线程不安全最常见的原因


🌷系统角度分析线程不安全的原因

内存可见性

一些线程多数据的操作,很可能其他线程是无法感知的,甚至某些情况下,会被优化到完全看不到的结果,当有共享内存存在时,就会导致同一个值被多次操作

代码重排序

我们写的程序,往往是经过中间很多环节优化的结果,并不保证最终执行的语句和我们写的语句是一模一样的

所谓的重排序,就是指:执行的指令和书写的指令不一致


目录
相关文章
|
3月前
|
Java 开发者
Java面试题:请解释内存泄漏的原因,并说明如何使用Thread类和ExecutorService实现多线程编程,请解释CountDownLatch和CyclicBarrier在并发编程中的用途和区别
Java面试题:请解释内存泄漏的原因,并说明如何使用Thread类和ExecutorService实现多线程编程,请解释CountDownLatch和CyclicBarrier在并发编程中的用途和区别
49 0
|
2月前
|
Java 开发者
奇迹时刻!探索 Java 多线程的奇幻之旅:Thread 类和 Runnable 接口的惊人对决
【8月更文挑战第13天】Java的多线程特性能显著提升程序性能与响应性。本文通过示例代码详细解析了两种核心实现方式:Thread类与Runnable接口。Thread类适用于简单场景,直接定义线程行为;Runnable接口则更适合复杂的项目结构,尤其在需要继承其他类时,能保持代码的清晰与模块化。理解两者差异有助于开发者在实际应用中做出合理选择,构建高效稳定的多线程程序。
53 7
|
1月前
|
存储 Java 程序员
优化Java多线程应用:是创建Thread对象直接调用start()方法?还是用个变量调用?
这篇文章探讨了Java中两种创建和启动线程的方法,并分析了它们的区别。作者建议直接调用 `Thread` 对象的 `start()` 方法,而非保持强引用,以避免内存泄漏、简化线程生命周期管理,并减少不必要的线程控制。文章详细解释了这种方法在使用 `ThreadLocal` 时的优势,并提供了代码示例。作者洛小豆,文章来源于稀土掘金。
【多线程面试题 二】、 说说Thread类的常用方法
Thread类的常用方法包括构造方法(如Thread()、Thread(Runnable target)等)、静态方法(如currentThread()、sleep(long millis)、yield()等)和实例方法(如getId()、getName()、interrupt()、join()等),用于线程的创建、控制和管理。
|
2月前
|
SQL 机器学习/深度学习 算法
【python】python指南(一):线程Thread
【python】python指南(一):线程Thread
40 0
|
3月前
|
Java C# Python
线程等待(Thread Sleep)
线程等待(Thread Sleep)
|
4月前
|
存储 设计模式 安全
C++一分钟之-并发编程基础:线程与std::thread
【6月更文挑战第26天】C++11的`std::thread`简化了多线程编程,允许并发执行任务以提升效率。文中介绍了创建线程的基本方法,包括使用函数和lambda表达式,并强调了数据竞争、线程生命周期管理及异常安全等关键问题。通过示例展示了如何用互斥锁避免数据竞争,还提及了线程属性定制、线程局部存储和同步工具。理解并发编程的挑战与解决方案是提升程序性能的关键。
77 3
|
4月前
|
Java
Java中,有两种主要的方式来创建和管理线程:`Thread`类和`Runnable`接口。
【6月更文挑战第24天】Java创建线程有两种方式:`Thread`类和`Runnable`接口。`Thread`直接继承受限于单继承,适合简单情况;`Runnable`实现接口可多继承,利于资源共享和任务复用。推荐使用`Runnable`以提高灵活性。启动线程需调用`start()`,`Thread`直接启动,`Runnable`需通过`Thread`实例启动。根据项目需求选择适当方式。
53 2
|
4月前
|
Java 开发者
JAVA多线程初学者必看:为何选择继承Thread还是Runnable,这其中有何玄机?
【6月更文挑战第19天】在Java中创建线程,可选择继承Thread类或实现Runnable接口。继承Thread直接运行,但限制了多重继承;实现Runnable更灵活,允许多线程共享资源且利于代码组织。推荐实现Runnable接口,以保持类的继承灵活性和更好的资源管理。
58 2
|
4月前
|
Java 开发者
告别单线程时代!Java 多线程入门:选继承 Thread 还是 Runnable?
【6月更文挑战第19天】在Java中,面对多任务需求时,开发者可以选择继承`Thread`或实现`Runnable`接口来创建线程。`Thread`继承直接但限制了单继承,而`Runnable`接口提供多实现的灵活性和资源共享。多线程能提升CPU利用率,适用于并发处理和提高响应速度,如在网络服务器中并发处理请求,增强程序性能。不论是选择哪种方式,都是迈向高效编程的重要一步。
40 2