Java多线程基础 - 华为云社区

2025-12-24 13:20:10 · 作者: AI Assistant · 浏览: 1

在Java编程中,多线程是提升应用性能和响应效率的重要手段。理解多线程的基本概念、实现方式及同步机制,对于开发高并发、高可靠的企业级应用至关重要。本文将深入剖析Java多线程的基础知识,包括并发与并行的差异、线程实现方式、同步锁的应用以及线程间的通信,帮助在校大学生和初级开发者构建扎实的多线程编程能力。

多线程的基本概念

Java多线程编程是实现并行处理的核心技术之一,其基础在于理解并发并行的区别。并行指的是多个CPU实例或多个机器同时执行处理逻辑,是真正的“同时”运行。而并发则是通过CPU调度算法,让多个任务在同一个CPU上交错执行,给人一种同时运行的错觉。并发常常涉及公共资源的访问,对这些资源的协调处理是并发编程的难点。

进程与线程

进程是操作系统中运行的一个应用程序,它拥有独立的内存空间和系统资源。多进程指的是同时运行多个任务程序,这在操作系统中是通过进程调度实现的。而线程则是操作系统能够进行运算调度的最小单位,它可以共享进程的资源,如内存、文件句柄等。多线程指的是在同一个进程中运行多个线程,它们可以独立执行不同的任务,从而提升程序的执行效率

多线程的应用场景

多线程在实际开发中有着广泛的应用场景,例如服务器同时处理多个用户请求、教学电脑控屏软件同时共享屏幕给多个设备等。这些场景都需要多线程来实现并行处理,提高响应速度和系统性能。

Java程序运行原理

Java程序的运行是基于Java虚拟机(JVM)的。当执行一个Java命令时,系统会启动JVM,从而启动一个应用程序进程。该进程会自动启动一个“主线程”,并调用某个类的main方法来开始程序的执行。

JVM的启动方式

JVM的启动是多线程的。在JVM启动时,至少会启动垃圾回收线程主线程垃圾回收线程负责管理内存,而主线程则负责执行程序的主逻辑。因此,JVM的启动过程本身就是多线程的体现。

实现多线程的两种方式

Java提供了两种实现多线程的方式:继承Thread类实现Runnable接口。这两种方式各有优劣,适用于不同的场景。

继承Thread类

  1. 定义一个类继承Thread
  2. 重写run方法
  3. 将线程需要执行的任务写在run方法中。
  4. 创建线程对象
  5. 调用start()方法开启新线程。

这种方式的优点在于可以直接使用Thread类中的方法,代码较为简洁。但其弊端是无法继承其他类,因为Java不支持多继承。如果已经有一个父类,就不能再使用这种方式。

实现Runnable接口

  1. 定义一个类实现Runnable接口
  2. 实现run方法
  3. 将线程需要执行的任务写在run方法中。
  4. 创建Runnable的子类对象
  5. 创建Thread对象,并传入Runnable对象
  6. 调用start()方法开启新线程。

这种方式的优点在于可以继承其他类,因为接口可以多实现。但其弊端在于需要获取线程对象才能调用Thread中的方法,使得代码相对复杂。

两种实现方式的区别

特性 继承Thread类 实现Runnable接口
继承限制 无法继承其他类 可以继承其他类
代码复杂度 相对简单 相对复杂
线程共享 直接共享 需要通过对象传递

继承Thread类适合简单场景,而实现Runnable接口更适合需要共享资源或继承其他类的复杂场景。选择哪种方式,取决于具体的应用需求和代码结构。

线程同步(同步锁)

在多线程环境中,线程同步是保证数据一致性的重要手段。当多个线程并发执行时,如果存在共享资源,就可能引发数据竞争,导致不一致错误结果

同步代码块

同步代码块使用synchronized关键字加上一个锁对象来定义,这种代码块被称为同步代码块。多个同步代码块如果使用相同的锁对象,则它们是同步的,同一时间只能执行一段代码,直到该段代码执行完毕,其他线程才能继续执行。

同步锁的应用

同步锁的应用场景包括共享数据的修改资源的访问等。例如,在买票系统中,多个售票窗口同时访问共享的票数变量,就需要使用同步锁来防止竞态条件

线程同步的实例

以下是一个买票系统的示例,展示了如何使用同步锁来防止多个线程同时修改共享资源:

public class Demo4 {
    public static void main(String[] args) {
        // 创建4个售票窗口
        TicketThread t1 = new TicketThread("A");
        TicketThread t2 = new TicketThread("B");
        TicketThread t3 = new TicketThread("C");
        TicketThread t4 = new TicketThread("D");
        // 开启线程
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
class TicketThread extends Thread {
    // 共享定义100张票
    static int tickets = 100;

    public TicketThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized ("nihao") {
                if (tickets > 0) {
                    System.out.println(Thread.currentThread().getName() + "已售票号" + tickets--);
                } else {
                    break;
                }
            }
        }
    }
}

在这个例子中,synchronized关键字使用了一个锁对象“nihao”,确保多个线程在访问共享资源时不会同时进行,从而避免了数据竞争。

同步线程之间的通信

在多线程环境中,线程通信是实现线程协作的重要手段。当多个线程并发执行时,CPU的调度是随机的,为了实现有规律的执行,可以使用等待唤醒机制

通信的必要性

多个线程并发执行时,默认情况下,CPU是随机切换线程的。为了实现有规律的执行,例如每个线程执行一次打印,就需要使用通信机制。

通信的实现方式

线程通信主要通过wait()notify()方法实现。wait()方法用于让线程等待,notify()方法用于唤醒等待的线程。这两个方法必须在同步代码块中执行,且使用同步锁对象来调用。

线程通信的实例

以下是一个实现ABABAB型输出的示例,展示了如何使用wait()notify()方法进行线程通信:

public class Demo1 {
    public static void main(String[] args) {
        // 创建打印对象
        final Shuchu sc = new Shuchu();
        new Thread(() -> {
            while (true) {
                try {
                    sc.scw1();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(() -> {
            while (true) {
                try {
                    sc.scw2();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
class Shuchu {
    private int flag = 1;

    public void scw1() throws InterruptedException {
        synchronized (this) {
            while (flag != 1) {
                this.wait();
            }
            System.out.print("1");
            System.out.print("2");
            System.out.print("3");
            System.out.print("4");
            System.out.println("5");
            flag = 2;
            this.notify();
        }
    }

    public void scw2() throws InterruptedException {
        synchronized (this) {
            while (flag != 2) {
                this.wait();
            }
            System.out.print("A");
            System.out.print("B");
            System.out.print("C");
            System.out.print("D");
            System.out.println("E");
            flag = 1;
            this.notify();
        }
    }
}

在这个例子中,flag变量用于控制线程的执行顺序,wait()notify()方法用于实现线程间的通信,确保输出顺序为ABABAB

线程通信的关键点

在使用wait()notify()方法进行线程通信时,有几个关键点需要注意:

  1. wait()方法notify()方法必须在同步代码块中调用,且使用同一个同步锁对象
  2. wait()方法可以带参数,表示等待的时间,如果没有参数则一直等待。
  3. sleep()方法同步中不释放锁,而wait()方法同步中释放锁。这意味着在使用wait()方法时,线程会释放锁,允许其他线程执行,而在使用sleep()方法时,线程不会释放锁,其他线程无法进入同步代码块。

面试题:sleep()和wait()的区别

  • sleep()必须传入参数,参数是时间,时间到了能自己醒来。
  • wait()不是必须要传入参数,如果没有参数就等待,如果传入参数则在时间到了后等待。
  • sleep()同步中不释放锁,而wait()同步中释放锁

总结与建议

Java多线程编程是提升应用性能和响应效率的核心技术之一。理解并发并行的区别、线程的实现方式同步锁的应用以及线程通信机制,对于开发高并发、高可靠的企业级应用至关重要。在校大学生和初级开发者应注重同步机制线程通信的实践,以提升代码的安全性和可靠性

在实践中,继承Thread类适合简单的多线程场景,而实现Runnable接口更适合需要共享资源或继承其他类的复杂场景。选择哪种方式,取决于具体的应用需求和代码结构。

此外,线程通信是实现线程协作的重要手段,wait()notify()方法的正确使用可以确保线程按照预期顺序执行。建议在学习和实践中多加练习,熟悉这些概念和方法的使用,以提升自己的多线程编程能力。

关键字列表:Java多线程, 同步锁, 线程通信, 并发与并行, 线程实现方式, JVM启动, 线程同步, 线程执行顺序, sleep与wait区别, 线程安全