【Thread类的用法(1)-- Thread类的基本用法】

Thread类的用法(1)--Thread类的基本用法

Java中创建线程的方式

在Java中,创建并启动一个线程有多种方式,下面通过5个代码案例逐一讲解。这些案例都实现了同一个功能:主线程每隔1秒打印"hello main",子线程每隔1秒打印"hello thread"。

方式一:继承Thread类,重写run方法

java 复制代码
package thread;

class MyThread extends Thread{
    //继承thread类,重写run方法
    //run方法代表线程的入口,线程执行起来后,要执行什么样的逻辑
    @Override
    public void run() {
        while (true) {
            System.out.println("hello thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        //使用这个线程
        MyThread t = new MyThread();
        //启用这个线程
        t.start();

        while(true) {
            System.out.println("hello,world");
            Thread.sleep(1000);
        }
    }
}

解释 :这是最传统的写法。我们定义一个类MyThread,让它继承Thread类,并重写run()方法------run()就是线程的入口方法,线程启动后会执行里面的逻辑。在main方法中,先创建MyThread实例,然后调用start()方法启动线程(注意:直接调用run()不会启动新线程,只是普通方法调用)。启动后,主线程和子线程会并发执行,各自打印自己的内容。

优点 :代码直观,适合简单的线程任务。

缺点 :Java是单继承,继承了Thread就不能再继承其他类,扩展性差。

代码运行结果如下:

方式二:实现Runnable接口,实现run方法

java 复制代码
package thread;
class MyRunnable implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("hello thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

public class Demo2 {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        // runnable.start();  // 错误!Runnable没有start方法
        Thread t = new Thread(runnable);
        t.start();

        while (true) {
            System.out.println("hello world");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

解释 :这种方式将"任务"和"线程"分离开。MyRunnable只负责定义要执行的任务(实现run()方法),它本身不是线程。要启动这个任务,需要创建一个Thread对象,并把MyRunnable实例传进去,然后调用Threadstart()方法。注意注释中写的runnable.start()是错误的------Runnable接口没有start()方法。

优点:Java支持实现多个接口,不影响类的继承体系,更灵活。适合多个线程共享同一个任务实例的场景。


方式三:使用匿名内部类,继承Thread类

java 复制代码
package thread;
public class Demo3 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread() {
            // 子类中的属性和方法.

            @Override
            public void run() {
                while (true) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };

        t.start();

        while (true) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

解释 :这是方式一的简化版。我们不再单独定义一个MyThread类,而是直接在new Thread()后面跟一对大括号{},在里面重写run()方法。这个{}就是一个匿名内部类,它相当于创建了一个Thread的子类,但类名是匿名的。这种方式适合线程逻辑比较简单、只在当前代码中使用一次的场景。

优点 :代码更紧凑,不需要额外定义类。

缺点:如果线程逻辑复杂,匿名内部类会让代码可读性变差。

代码运行结果如下:


方式四:使用匿名内部类,实现Runnable接口

java 复制代码
package thread;

public class Demo4 {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };

        Thread t = new Thread(runnable);

        t.start();

        while (true) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

解释 :这是方式二的简化版。我们不再单独定义MyRunnable类,而是用匿名内部类的方式直接创建Runnable接口的实现。new Runnable() { ... }创建了一个实现了Runnable接口的匿名对象,然后把这个对象传给Thread的构造方法。这种方式结合了方式二的灵活性和匿名内部类的简洁性,是实际开发中非常常见的写法。

优点:既保留了实现接口的灵活性,又省去了单独定义类的步骤。

代码运行结果如下:


方式五:使用Lambda表达式

java 复制代码
package thread;

public class Demo5 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (true) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        t.start();

        while (true) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

解释 :这是Java 8引入的Lambda表达式写法,是方式四的进一步简化。Runnable接口是一个函数式接口(只有一个抽象方法run()),所以可以用Lambda表达式() -> { ... }来直接表示它的实现。()对应run()方法的参数(无参),->后面的大括号就是run()的方法体。这是目前最简洁、最推荐的写法。

优点 :代码最简洁,可读性高,是Java 8及以后版本的首选写法。

缺点:初学者需要先理解Lambda表达式的语法。

代码运行结果如下:


Lambda表达式中的变量捕获:必须是事实常量

在使用Lambda表达式时,有一个非常重要的约束:Lambda表达式中引用的局部变量必须是事实常量(effectively final)。也就是说,这个变量一旦被赋值后,就不能再被修改------无论是在Lambda表达式内部还是外部,否则编译器会直接报错。

来看一个错误的例子:

java 复制代码
public class DemoError {
    public static void main(String[] args) {
        int count = 0;  // 这个变量后续被修改了

        Thread t = new Thread(() -> {
            while (true) {
                System.out.println("hello thread, count = " + count);
                // count++;  // 这里也会报错,不能在Lambda内部修改
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        count = 10;  // ❌ 编译错误!count被重新赋值,不再是事实常量
        t.start();
    }
}

上面的代码会编译失败,错误信息类似于:Variable used in lambda expression should be effectively final。因为count在声明后被重新赋值为10,它就不再是事实常量了。

正确的做法 :如果需要在Lambda中使用一个值,要么声明时就加上final关键字,要么确保变量在初始化后不再被修改:

java 复制代码
public class DemoCorrect {
    public static void main(String[] args) {
        int count = 0;  // 初始化后不再修改,是事实常量
        // count = 10;  // 如果取消注释这行,就会报错

        Thread t = new Thread(() -> {
            while (true) {
                System.out.println("hello thread, count = " + count);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        t.start();
    }
}

为什么要有这个约束?

这涉及到Java的内存模型。Lambda表达式本质上是一个匿名函数,它可能会在另一个线程中执行。如果Lambda表达式引用的局部变量可以被随意修改,那么多个线程同时访问这个变量时就会出现线程安全问题。Java通过要求变量是事实常量,保证了Lambda表达式内部看到的变量值是一致的、不会发生并发修改的混乱。

注意 :这个约束只针对局部变量 。如果Lambda表达式访问的是类的成员变量(实例变量或静态变量),则没有"事实常量"这个编译期限制------你可以在Lambda内部或外部随意修改成员变量的值,编译器不会报错。

但这并不意味着在多线程环境下访问成员变量就是安全的。恰恰相反,这里隐藏着更大的风险:

1. 成员变量可以被随意修改,导致线程安全问题

java 复制代码
public class UnsafeCounter {
    private int count = 0;  // 成员变量,不是事实常量

    public void startThread() {
        Thread t = new Thread(() -> {
            while (true) {
                count++;  // ✅ 编译器不会报错,但存在线程安全问题
                System.out.println("count = " + count);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
    }
}

上面的代码可以正常编译,但count++不是原子操作,多个线程同时访问时会出现数据不一致的问题。你需要自己用synchronizedvolatileAtomicInteger等机制来保证线程安全。

2. 对象被销毁的风险

Lambda表达式捕获的是this引用(即当前对象的引用),而不是成员变量的副本。这意味着Lambda表达式内部持有的是对外部对象的隐式引用。如果这个外部对象在其他地方被置为null或超出了作用域,而Lambda表达式所在的线程还在运行,就可能出现意想不到的问题:

java 复制代码
public class DangerousExample {
    private String message = "hello";

    public void startThread() {
        Thread t = new Thread(() -> {
            try {
                Thread.sleep(5000);  // 等待5秒
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            // 5秒后,外部对象可能已经被销毁
            System.out.println(message);  // 可能抛出NullPointerException
        });
        t.start();
    }

    public static void main(String[] args) {
        new DangerousExample().startThread();
        // main方法执行完毕,DangerousExample对象可能被GC回收
        // 但Lambda表达式仍然持有对它的引用
        System.gc();  // 强制GC,可能导致对象被回收
    }
}

虽然Lambda表达式持有外部对象的引用可以防止对象被GC回收(只要Lambda还在运行,外部对象就不会被回收),但如果你在Lambda执行期间修改了成员变量的值,或者外部对象的状态发生了变化,Lambda内部看到的数据可能已经不是预期的数据了。

总结:Lambda表达式访问成员变量时,编译器不会强制要求"事实常量",但这并不意味着安全。你需要自己负责:

  • 使用同步机制保证成员变量的线程安全
  • 注意Lambda对外部对象的隐式引用,避免状态不一致
  • 如果可能,优先将需要的值作为局部变量捕获(利用事实常量的约束),而不是直接访问成员变量

总结对比

继承Thread vs 实现Runnable:核心区别

理解这两种方式的本质区别,对掌握Java多线程非常重要。

1. 本质不同:一个是类,一个是接口

继承Thread,意味着你创建了一个Thread的子类,这个子类本身就是一个线程对象。你可以直接调用start()方法启动它:

java 复制代码
MyThread t = new MyThread();
t.start();  // 直接调用,因为MyThread是Thread的子类

而实现Runnable,只是定义了一个"任务"------你规定了线程要执行的逻辑(run()方法),但这个任务本身并不是线程。Runnable接口中只有一个run()方法,没有start()方法,所以你不能这样写:

java 复制代码
MyRunnable runnable = new MyRunnable();
runnable.start();  // ❌ 编译错误!Runnable接口中没有start()方法

正确的做法是把Runnable实例传给Thread的构造方法,由Thread来启动:

java 复制代码
MyRunnable runnable = new MyRunnable();
Thread t = new Thread(runnable);
t.start();  // ✅ 由Thread对象来启动线程

2. 继承体系的影响

Java是单继承的。如果继承了Thread,就不能再继承其他类,这限制了类的扩展性。而实现Runnable接口则没有这个限制,你的类还可以继承其他类或实现更多接口。

3. 任务与线程的分离程度

实现Runnable将"要执行的任务"和"执行任务的线程"解耦了。同一个Runnable实例可以传给多个Thread对象,实现多个线程共享同一份任务逻辑。而继承Thread的方式,每个线程实例都绑定了一个特定的任务类,复用性较差。

4. 适用场景

  • 继承Thread:适合简单的、不需要再继承其他类的场景,代码直观,适合初学者理解线程的基本概念。
  • 实现Runnable:更灵活,是实际开发中更推荐的方式,尤其适合需要共享任务实例或类已有继承关系的场景。
方式 核心思想 代码量 推荐场景
继承Thread 类继承 较多 简单任务,不关心扩展性
实现Runnable 接口实现 较多 需要灵活扩展,或共享任务
匿名内部类-继承Thread 匿名子类 中等 一次性使用的线程
匿名内部类-实现Runnable 匿名实现 中等 一次性使用的任务,兼顾灵活性
Lambda表达式 函数式编程 最少 现代Java首选

从方式一到方式五,代码越来越简洁,体现了Java语言从面向对象到函数式编程的演进趋势。在实际开发中,方式五(Lambda表达式) 是最推荐的做法,但理解前四种写法有助于你深入理解Java线程的底层机制。

start()方法和run()方法的区别

很多初学者容易混淆start()run()这两个方法,下面通过代码和原理来彻底讲清楚它们的区别。

1. 本质区别:启动线程 vs 定义任务

  • start() :它的作用是启动一个新线程 。调用start()后,JVM会创建一个新的线程,并让这个新线程去执行run()方法中的代码。start()方法会立即返回,不会阻塞调用它的线程。
  • run() :它的作用是定义线程要执行的任务逻辑 。它只是一个普通的方法,里面封装了线程启动后要执行的代码。直接调用run()不会创建新线程,它会在当前线程中像普通方法一样被顺序执行。

2. 代码演示:直接调用run() vs 调用start()

来看一个对比示例,直观感受两者的区别:

java 复制代码
public class StartVsRunDemo {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println("当前线程:" + Thread.currentThread().getName());
        });

        System.out.println("=== 直接调用 run() ===");
        t.run();  // 不会启动新线程,在main线程中执行

        System.out.println("=== 调用 start() ===");
        t.start();  // 启动新线程,在新线程中执行
    }
}

运行结果如下:

复制代码
=== 直接调用 run() ===
当前线程:main          ← 注意:是main线程在执行
=== 调用 start() ===
当前线程:Thread-0      ← 新创建的线程在执行

从输出可以清楚看到:

  • 直接调用t.run()时,run()方法中的代码是在**主线程(main)**中执行的,没有创建新线程。
  • 调用t.start()时,JVM创建了一个名为Thread-0的新线程,由这个新线程去执行run()方法中的代码。

3. 为什么不能直接调用run()来启动线程?

因为run()只是一个普通的方法,它不具备创建新线程的能力。创建新线程是start()方法的职责start()方法内部会做以下几件事:

  1. 检查线程状态 :确保线程没有被重复启动(一个线程只能调用一次start(),重复调用会抛出IllegalThreadStateException)。
  2. 加入线程组:将当前线程加入线程组中管理。
  3. 调用本地方法start0() :这是一个JVM层面的本地方法(native method),它会向操作系统申请创建一个新的线程,并让这个新线程去执行run()方法。

run()方法只是一个普通的实例方法,直接调用它就是在当前线程中执行,不会触发上述任何线程创建流程。

4. 一个线程只能调用一次start()

java 复制代码
Thread t = new Thread(() -> System.out.println("hello"));
t.start();
t.start();  // ❌ 抛出 IllegalThreadStateException

第二次调用start()会抛出异常,因为线程的状态已经从NEW变为RUNNABLE,不允许再次启动。而run()方法可以反复调用,因为它只是一个普通方法。

5. 总结对比

对比项 start() run()
作用 启动一个新线程 定义线程要执行的任务
是否创建新线程 ✅ 是,由JVM创建 ❌ 否,在当前线程中执行
调用次数限制 只能调用一次 可以多次调用
调用方式 Thread对象调用 Thread对象调用,或由Runnable实例调用
底层实现 调用JVM本地方法start0() 普通Java方法

一句话总结start()负责创建并启动线程run()负责规定线程启动后要做什么 。永远不要直接调用run()来"启动"线程------那只是普通的方法调用,不会产生多线程效果。

Thread类的基本用法

前面我们学习了创建线程的5种方式,以及start()run()的区别。接下来,我们来系统学习Thread类的一些基本用法,包括线程的创建、中断、等待、休眠以及获取线程实例等操作。

1. 线程创建

创建线程最核心的一步就是实例化一个Thread对象 ,并调用它的start()方法启动。前面我们已经通过5种方式演示了如何创建线程,这里不再赘述。简单回顾一下核心流程:

java 复制代码
// 1. 定义任务(Runnable)
Runnable task = () -> {
    System.out.println("线程执行中...");
};

// 2. 创建Thread对象,传入任务
Thread t = new Thread(task);

// 3. 启动线程
t.start();

2. 获取线程实例与判断存活状态

在Java中,可以通过Thread.currentThread()获取当前正在执行的线程实例。而isAlive()方法可以判断一个线程是否还存活(即是否已经启动且尚未终止)。

java 复制代码
package thread;

public class Demo7 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
               for(int i = 0; i < 3; i++) {
                   System.out.println("hello thread");
                   try {
                       Thread.sleep(1000);
                   } catch (InterruptedException e) {
                       throw new RuntimeException(e);
                   }
               }
        });

        // 获取 t 的存活状态
        System.out.println(t.isAlive());

        t.start();

        Thread.sleep(1000);
        // 此时 t 还在运行中
        System.out.println(t.isAlive());

        Thread.sleep(4000);
        // 此时 t 已经结束了
        System.out.println(t.isAlive());
    }
}

解释

  • t.start()之前调用t.isAlive(),返回false,因为线程还没有启动。
  • 启动后1秒,线程还在执行循环,isAlive()返回true
  • 再等4秒,线程的循环已经执行完毕(3次循环,每次sleep 1秒,总共约3秒),线程终止,isAlive()返回false

注意isAlive()返回false只表示线程已经终止,不代表线程对象被回收。线程对象仍然存在,只是不能再被重新启动。

3. 线程中断

线程中断是一种协作式的机制------一个线程可以向另一个线程发送"中断请求",但目标线程是否响应、何时响应,完全由目标线程自己决定。Java提供了两种常见的中断方式:

方式一:使用标志位(自定义变量)

通过一个共享的boolean变量来控制线程的终止。这种方式简单直观,但需要注意volatile关键字保证变量的可见性。

java 复制代码
package thread;

import java.util.Scanner;

public class Demo9 {
    private static volatile boolean running = true;

    public static void main(String[] args) {

        Thread t = new Thread(() -> {
            while (running) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("thread 结束");
        });

        t.start();

        Scanner scanner = new Scanner(System.in);
        System.out.println("输入任意内容, 控制 t 线程结束: ");
        scanner.next();
        running = false;  // 修改标志位,t线程检测到后退出循环
    }
}

解释

  • running是一个被volatile修饰的共享变量,保证了多个线程之间的可见性。
  • 子线程在while (running)循环中不断执行任务,当主线程将running置为false时,子线程检测到条件不满足,退出循环,线程自然结束。
  • 这种方式需要我们自己定义标志位,适合简单的场景。
方式二:使用interrupt()方法(官方推荐)

Thread类内置了中断机制,通过interrupt()方法发送中断请求,目标线程通过isInterrupted()Thread.interrupted()来检测中断状态。

java 复制代码
package thread;

import java.util.Scanner;

public class Demo10 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // 1. 立即终止线程
                    // break;

                    // 2. 稍后处理
                    // 先执行一些其他操作, 比如释放资源收尾操作, 然后再 break
                    // break;

                    // 3. 不处理, 忽略了终止请求.
                    continue;
                }
            }
            System.out.println("线程 t 终止");
        });

        t.start();

        System.out.println("输入任意内容, 终止 t 线程");
        Scanner scanner = new Scanner(System.in);
        scanner.next();

        // 终止 t 线程
        t.interrupt();
    }
}

解释

  • Thread.currentThread().isInterrupted():检测当前线程是否被中断。如果返回true,说明有其他线程调用了当前线程的interrupt()方法。
  • 当线程处于sleep()wait()join()阻塞状态 时,如果其他线程调用了该线程的interrupt()方法,会抛出InterruptedException异常。此时线程的中断标志位会被清除(置为false),我们需要在catch块中决定如何处理:
    • 立即终止 :直接break退出循环。
    • 稍后处理 :先执行一些收尾操作(如释放资源),再break
    • 忽略请求continue继续执行,不响应中断。

两种方式的对比

方式 优点 缺点
自定义标志位 简单直观,容易理解 需要自己处理可见性(volatile),无法中断sleep等阻塞状态
interrupt() 官方API,能中断阻塞状态,更规范 需要处理InterruptedException,逻辑稍复杂

4. 线程休眠

Thread.sleep(long millis)是一个静态方法,让当前正在执行的线程暂停执行指定的毫秒数 ,进入TIMED_WAITING状态。休眠期间,线程不会占用CPU资源。

java 复制代码
try {
    Thread.sleep(1000);  // 当前线程休眠1秒
} catch (InterruptedException e) {
    // 如果在休眠期间被中断,会抛出InterruptedException
    e.printStackTrace();
}

注意事项

  • sleep()会抛出InterruptedException,必须捕获或声明抛出。
  • sleep()不会释放锁(如果线程持有锁,休眠期间其他线程仍然无法获取该锁)。
  • 休眠时间不保证绝对精确,受操作系统调度影响。

5. 线程等待(join)

join()方法让当前线程等待目标线程执行完毕 。如果线程A中调用了t.join(),那么线程A会阻塞,直到线程t执行完毕(或超时)才继续执行。

java 复制代码
Thread t = new Thread(() -> {
    for (int i = 0; i < 3; i++) {
        System.out.println("子线程执行中...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});

t.start();
t.join();  // 主线程等待t线程执行完毕
System.out.println("子线程已结束,主线程继续执行");

解释

  • 主线程调用t.join()后,会进入WAITING状态,直到t线程执行完毕。
  • join()也提供了带超时参数的版本:join(long millis),表示最多等待指定的毫秒数,超时后无论目标线程是否结束,当前线程都会继续执行。
  • join()同样会抛出InterruptedException,需要处理。

6. 线程的不可重复启动

一个线程对象只能调用一次start()方法 。如果重复调用,会抛出IllegalThreadStateException

java 复制代码
package thread;

public class Demo8 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println("hello thread");
        });

        t.start();
        System.out.println("线程第一次启动");
        t.start();  // ❌ 抛出 IllegalThreadStateException
        System.out.println("线程第二次启动");
    }
}

解释

  • 第一次调用t.start()成功启动线程,线程状态从NEW变为RUNNABLE
  • 第二次调用t.start()时,线程已经不是NEW状态,JVM会抛出IllegalThreadStateException
  • 如果需要再次执行相同的任务,必须重新创建一个新的Thread对象。

总结

方法 作用 说明
start() 启动线程 只能调用一次,重复调用抛异常
isAlive() 判断线程是否存活 线程启动后到终止前返回true
sleep(ms) 让当前线程休眠 静态方法,不释放锁
join() 等待目标线程结束 当前线程阻塞,直到目标线程执行完毕
interrupt() 发送中断请求 协作式中断,目标线程自行决定是否响应
isInterrupted() 检测中断状态 不会清除中断标志位
Thread.currentThread() 获取当前线程实例 静态方法,常用于Lambda内部获取当前线程引用

这些是Thread类最常用的基本用法,掌握它们之后,你就能灵活地控制线程的创建、执行、休眠、等待和终止了。

相关推荐
suconnect2 小时前
Spring Boot接入企业RAG:文档切分、向量检索、权限过滤和答案溯源
java·spring boot·后端
weixin_BYSJ19872 小时前
springboot校园自习室管理小程序---附源码32142
java·javascript·spring boot·python·django·flask·php
ClickHouseDB2 小时前
ClickHouse托管Postgres:OLTP+OLAP,新能力解锁最佳数据平台
java·前端·数据库
霸道流氓气质2 小时前
Java中集成Smile 技术教程:从入门到工程实践
java·开发语言
wuqingshun3141593 小时前
MyBatis 中#()和$()的区别是什么?
java
小Ti客栈3 小时前
SpringBoot 事件发布机制:原理与应用
java·spring boot·spring
BerrySen1783 小时前
迈向 Next-Gen Java:企业级高并发架构演进与大模型 Agent 落地深度实战
java·开发语言·架构
technology_x3 小时前
2026年财务报表分析软件测评:兼容与安全解析
java·服务器·前端
兰令水3 小时前
hot100【acm版】【2026.7.25/26打卡-java版本】
java·算法·排序算法