什么是线程Thread

线程(Thread) 是操作系统能够进行调度的最小执行单元。可以把它理解为程序内部的一条独立执行路径

通俗理解(银行办业务):

假设一家公司要去银行办理业务,既要财务转账,又要福利发放,还得缴社保:

单线程模式:只有张三一个会计,他需要一个人依次完成三件事,耗时很长。

多线程模式:张三叫来李四、王五一起帮忙。三个人分别负责一件事,各自排队办理。三个人都是独立的“执行流”,共同为这家公司服务。

张三就是主线程(Main Thread)

李四、王五就是新创建的子线程

三个执行流并发执行,提高了整体效率

为什么需要多线程

① 充分利用多核CPU

单核CPU的发展已遇瓶颈,现在都是多核CPU。多线程能充分利用多核资源,提高程序运行效率。

② 处理IO等待

当程序需要等待IO操作(如网络请求、文件读写)时,CPU可以切换到其他线程执行任务,避免CPU空闲等待。

③线程比进程更轻量,调度开销和资源占用都更少。

线程和进程的区别

进程是包含线程的,每个进程最少有一个线程存在即主线程。

进程和进程之间补共不内存空间,同一个进程的线程之间共享内存空间。

进程是系统分配资源的最小空间,线程是系统调度的最小单位。

Java线程和操作系统线程的关系

线程是操作系统的概念。操作系统内核实现了线程机制,并提供API供用户使用(如Linux的pthread库)。

Java中的Thread类是对操作系统线程API的进一步抽象和封装,让我们可以跨平台地使用多线程。

创建线程的四种方法

1.继承Thread类来创建线程类

2.实现Runnable接口

3.匿名内部类创建Thread子类对象(或Runnable子类对象)

4.lambda表达式创建Runnable子类对象

注意:Lambda表达式要求接口是函数式接口(只有一个抽象方法),Runnable正好满足。

多线程的优势:提升运行速度

Thread类核心方法详解

1.常见构造方法

2.常见属性

3.启动线程

Thread t = new MyThread();
t.start();  // ✅ 在操作系统创建新线程,并执行run()

4.中断线程

一般有两种方法:用共享的标记进行沟通(添加volatile关键字)

public class ThreadDemo {
    private static class MyRunnable implements Runnable {
        public volatile boolean isQuit = false;  // volatile保证可见性

        @Override
        public void run() {
            while (!isQuit) {
                System.out.println("线程工作中...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("线程结束");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyRunnable target = new MyRunnable();
        Thread thread = new Thread(target);
        thread.start();
        
        Thread.sleep(5000);  // 5秒后通知停止
        target.isQuit = true;  // 修改标志位
    }
}

第二种是使用interrupt()方法

public class ThreadDemo {
    private static class MyRunnable implements Runnable {
        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("线程工作中...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // sleep被中断,清除中断标志,需要手动退出
                    System.out.println("收到中断信号,退出");
                    break;
                }
            }
            System.out.println("线程结束");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyRunnable target = new MyRunnable();
        Thread thread = new Thread(target);
        thread.start();
        
        Thread.sleep(5000);
        thread.interrupt();  // 线程对象调用interrupt方法,发送中断信号
    }
}

两种中断方式的区别

5.等待线程结束

当主线程需要等待子线程执行完毕后再继续时,使用join()。

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Runnable target = () -> {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + "工作中...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + "结束");
        };

        Thread t1 = new Thread(target, "李四");
        Thread t2 = new Thread(target, "王五");

        System.out.println("先让李四工作");
        t1.start();
        t1.join();  // 主线程等待李四完成
        
        System.out.println("李四结束了,再让王五工作");
        t2.start();
        t2.join();  // 主线程等待王五完成
        
        System.out.println("都结束了");
    }
}

6.获取当前线程引用

Thread.currentThread()

Thread current = Thread.currentThread();
System.out.println("当前线程名:" + current.getName());

7.线程休眠

sleep()

// 休眠3秒
Thread.sleep(3000);

// 更精确的休眠(纳秒级,但实际精度取决于操作系统)
Thread.sleep(3000, 500000);

注意:sleep()只能保证休眠时间不少于指定时间,实际唤醒时间取决于操作系统的线程调度。

线程状态

线程的状态是⼀个枚举类型Thread.State

NEW:安排了⼯作,还未开始⾏动

RUNNABLE:可⼯作的,⼜可以分成正在⼯作中和即将开始⼯作.

BLOCKED:这⼏个都表⽰排队等着其他事情• WAITING; TIMED_WAITING

TERMINATED:⼯作完成了.

状态转移

线程安全

如果多线程环境下代码运⾏的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是 线程安全的。

① 线程调度是抢占式的(随机性)

操作系统调度线程的顺序是不确定的,程序员必须保证在任意执行顺序下代码都能正常工作。

② 多个线程修改共享数据

上面的例子中,count是多个线程共享的变量。多个线程同时修改它,会导致数据不一致。

③ 操作不具备原子性
count++;  //

看起来是一行代码,实际是三步操作:
从内存读取count的值到CPU寄存器

CPU对值进行+1运算

将结果写回内存

如果线程A执行完第1步后被切换,线程B修改了count,然后线程A继续执行第2、3步,就会覆盖B的修改,导致数据丢失

Java内存模型(JMM)

⽬的是屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到⼀致的 并发效果.

线程之间的共享变量存在 主内存 (Main Memory).

每⼀个线程都有⾃⼰的 "⼯作内存" (Working Memory).

当线程要读取⼀个共享变量的时候,会先把变量从主内存拷⻉到⼯作内存,再从⼯作内存读取数据.

当线程要修改⼀个共享变量的时候,也会先修改⼯作内存中的副本,再同步回主内存

synchronized详解

synchronized特性:

1.互斥性

synchronized会起到互斥效果,某个线程执⾏到某个对象的synchronized中时,其他线程如果也执⾏ 到同⼀个对象synchronized就会阻塞等待.

进⼊synchronized修饰的代码块,相当于加锁

 退出synchronized修饰的代码块,相当于解锁

类比:厕所门上的“有人/无人”标识:

无人 → 可以进入,设置为“有人”

有人 → 其他人只能在门外排队等待

public class ThreadSafe {
    private static int count = 0;
    private static final Object locker = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                synchronized (locker) {
                    count++;
                }
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                synchronized (locker) {
                    count++;
                }
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("count: " + count);  // 稳定输出100000
    }
}

2.可重入性

同一个线程可以多次获取同一把锁,不会死锁。

synchronized (locker) {
    synchronized (locker) {  // 同一线程再次获取锁,允许!
        count++;
    }
}

在可重⼊锁的内部,包含了"线程持有者"和"计数器"两个信息.

实现原理:每个锁内部记录持有线程计数器

  • 加锁时,如果是同一个线程,计数器+1

  • 解锁时,计数器-1,减到0时才真正释放锁

public class SynchronizedDemo {
    private final Object locker = new Object();

    // 1. 锁指定对象
    public void method1() {
        synchronized (locker) {
            // 业务代码
        }
    }

    // 2. 锁当前对象 (this)
    public synchronized void method2() {
        // 业务代码
    }

    // 3. 锁类的Class对象
    public synchronized static void method3() {
        // 业务代码
    }
}

线程安全类

String是不可变类,本身线程安全

StringBuffer内部方法加了synchronized,但已不推荐使用,单线程用StringBuilder,多线程用其他方式同步

volatile关键字

volatile修饰的变量,能够保证"内存可⻅性".

前⾯我们讨论内存可⻅性时说了,直接访问⼯作内存(实际是CPU的寄存器或者CPU的缓存),速度⾮ 常快,但是可能出现数据不⼀致的情况.

volatile修饰的变量,读写操作直接在主内存进行,不经过工作内存(CPU缓存)

static class Counter {
    public volatile int flag = 0;
}

public static void main(String[] args) {
    Counter counter = new Counter();
    
    Thread t1 = new Thread(() -> {
        while (counter.flag == 0) {
            // 循环等待
        }
        System.out.println("循环结束!");
    });
    
    Thread t2 = new Thread(() -> {
        Scanner scanner = new Scanner(System.in);
        System.out.println("输入一个整数:");
        counter.flag = scanner.nextInt();  // 用户输入非0时,t1结束
    });
    
    t1.start();
    t2.start();
}

如果flag不加volatile,t1可能永远看不到t2对flag的修改,导致死循环。加上volatile后,t1每次都能读到主内存中的最新值。

wait()和notify():线程间的通信

wait()的作用
释放当前锁,让出CPU

线程进入等待状态

其他线程调用notify()或notifyAll()后被唤醒

wait要搭配synchronized来使⽤.脱离synchronized使⽤wait会直接抛出异常.

notify()的作用
唤醒一个正在等待该锁的线程

被唤醒的线程需要重新竞争锁

public class WaitNotifyDemo {
    public static void main(String[] args) throws InterruptedException {
        Object locker = new Object();
        
        // 等待线程
        Thread t1 = new Thread(() -> {
            synchronized (locker) {
                try {
                    System.out.println("等待中...");
                    locker.wait();  // 释放锁,进入等待
                    System.out.println("被唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        
        // 唤醒线程
        Thread t2 = new Thread(() -> {
            synchronized (locker) {
                System.out.println("准备唤醒");
                locker.notify();  // 唤醒等待中的线程
                System.out.println("已唤醒");
            }
        });
        
        t1.start();
        Thread.sleep(1000);
        t2.start();
    }
}

notify():随机唤醒一个等待线程

notifyAll():唤醒所有等待线程(它们需要竞争锁)

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐