线程实现runnable和callable接口

实现 Runnable 接口

复制代码
public class Ticket implements Runnable {
    private int totalTickets = 10; // 总票数
    @Override
    public void run () {
        while (true) {
            synchronized (this) { // 同步锁,保证同一时间只有一个线程抢票
                if (totalTickets <= 0) break;
                System.out.println (Thread.currentThread ().getName () + "抢到第" + totalTickets + "张票");
                totalTickets--;
            }
            try {
                Thread.sleep (100); // 模拟抢票延迟
            } catch (InterruptedException e) {
                e.printStackTrace ();
            }
        }
    }
    public static void main (String [] args) {
        Ticket ticket = new Ticket ();
        // 创建 3 个线程模拟 3 个用户抢票
        new Thread (ticket, "用户 A").start ();
        new Thread (ticket, "用户 B").start ();
        new Thread (ticket, "用户 C").start ();
    }
}

实现 callable 接口(有返回值可抛出异常)

复制代码
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class TicketCallable implements Callable<String> {
    private int totalTickets = 10;
    @Override
    public String call () throws Exception {
        while (true) {
            synchronized (this) {
                if (totalTickets <= 0) {
                    return Thread.currentThread ().getName () + "没抢到票,票已售罄";
                }
                String result = Thread.currentThread ().getName () + "抢到第" + totalTickets + "张票";
                totalTickets--;
                Thread.sleep (100);
                return result; // 返回抢票结果
            }
        }
    }
    public static void main (String [] args) throws Exception {
        TicketCallable ticket = new TicketCallable ();
// 用 FutureTask 包装 Callable,才能传给 Thread
        FutureTask<String> task1 = new FutureTask<>(ticket);
        FutureTask<String> task2 = new FutureTask<>(ticket);
        FutureTask<String> task3 = new FutureTask<>(ticket);
        new Thread (task1, "用户 A").start ();
        new Thread (task2, "用户 B").start ();
        new Thread (task3, "用户 C").start ();
// 获取每个线程的抢票结果
        System.out.println (task1.get ());
        System.out.println (task2.get ());
        System.out.println (task3.get ());
    }
}

这里打印顺序不是按照 10-9-8,是和调度顺序有关,同一时刻只有 1 个线程进入同步代码块,抢到了票,但是没有来得及打印,cpu 的时间片就用完了。

这背后其实是FutureTask的工作机制在起作用。当我们提交Callable任务时,每个任务都会被包装成一个FutureTaskFutureTask在执行时,虽然内部也会竞争锁,但它还有一个额外的步骤,就是要保存任务的返回结果。这个保存结果的操作,加上FutureTask本身的状态管理,会引入一些微小的延迟。这些延迟会让各个线程的执行节奏变得更不一致,从而增加了打印顺序混乱的概率。

线程池

复制代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TicketThreadPool {
    private int totalTickets = 10;
    public static void main (String [] args) {
        TicketThreadPool ticket = new TicketThreadPool ();
// 创建一个固定大小为 3 的线程池,对应 3 个抢票用户
        ExecutorService executor = Executors.newFixedThreadPool (3);
// 提交 3 个抢票任务给线程池
        for (int i = 0; i < 3; i++) {
            executor.submit (() -> {
                while (true) {
                    synchronized (ticket) {
                        if (ticket.totalTickets <= 0) break;
                        System.out.println (Thread.currentThread ().getName () + "抢到第" + ticket.totalTickets + "张票");
                        ticket.totalTickets--;
                    }
                    try {
                        Thread.sleep (100);
                    } catch (InterruptedException e) {
                        e.printStackTrace ();
                    }
                }
            });
        }
// 关闭线程池
        executor.shutdown ();
    }
}

业务逻辑和线程池解耦,

比如以后修改抢票逻辑,线程池的代码就不用修改。在实际生产中直接在一个服务类或者工具类里,直接创建线程池即可,嵌入到合适的业务流程中就行。

复制代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TicketWithThreadPool {
    public static void main (String [] args) {
// 创建 Ticket 实例,包含抢票逻辑
        Ticket ticketTask = new Ticket ();
// 创建固定大小为 3 的线程池
        ExecutorService executor = Executors.newFixedThreadPool (3);
// 提交 3 个抢票任务,注意这里提交的是同一个 Ticket 实例
// 因为所有线程需要共享同一份票数,所以不能 new 三个 Ticket
        for (int i = 0; i < 3; i++) {
            executor.submit (ticketTask);
        }
// 任务提交完毕后关闭线程池
        executor.shutdown ();
    }
}

阿里军规-推荐使用 newFixedThreadPool

阿里巴巴的 Java 开发手册里确实有相关的建议。它之所以推荐用newFixedThreadPool这类封装好的方法,而不是让大家直接去 new ThreadPoolExecutor传一堆参数,主要就是为了避免踩坑。因为线程池的核心参数,比如核心线程数、最大线程数、存活时间、阻塞队列类型等,组合起来非常复杂,一旦设置不当,就可能导致内存泄漏、线程耗尽或者任务堆积等问题。对于大部分业务场景来说,newFixedThreadPool已经足够用了,它的线程数量固定,不会无限创建线程,相对更安全。

线程实现runnable和callable接口

相关推荐
一个处女座的程序猿O(∩_∩)O3 小时前
Nacos 中的 Namespace 深度解析:实现多租户隔离的关键机制
java
少控科技3 小时前
QT新手日记028 QT-QML所有类型
开发语言·qt
JavaGuide3 小时前
IntelliJ IDEA 2026.1 EAP 发布!拥抱 Java 26,Spring Boot 4 深度支持!
java·后端·mysql·springboot·idea·大厂面试·javaguide
HarmonLTS3 小时前
Python人工智能深度开发:技术体系、核心实践与工程化落地
开发语言·人工智能·python·算法
丁一郎学编程4 小时前
测试开发面经
java·开发语言
wjs20244 小时前
TypeScript 命名空间
开发语言
a程序小傲4 小时前
京东Java面试被问:RPC调用的熔断降级和自适应限流
java·开发语言·算法·面试·职场和发展·rpc·边缘计算
独自破碎E4 小时前
MyBatis Flex和MyBatis Plus的区别
java·开发语言·mybatis
葡萄成熟时 !4 小时前
正则表达式
java·正则表达式