线程实现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任务时,每个任务都会被包装成一个FutureTask。FutureTask在执行时,虽然内部也会竞争锁,但它还有一个额外的步骤,就是要保存任务的返回结果。这个保存结果的操作,加上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接口

相关推荐
哦虎!1 小时前
【JavaEE】网络初识
java
for_ever_love__2 小时前
MySQL ORDER BY 排序优化:从 Using filesort 到索引有序扫描(含双路 / 单路排序原理)
java·mysql·优化·存储·by·order·filesort
程序猿追2 小时前
kotlin-retry 接入鸿蒙后,怎样确认它真的会停止重试
开发语言·kotlin·harmonyos
我是人✓2 小时前
Java文件上传三方式对比:从传统到SpringBoot
java·开发语言
智能直播2 小时前
网络RTMP拉流不卡顿、声音不同步,一文讲透缓冲区、时间戳
开发语言·ffmpeg·php·音视频·视频编解码
欣栀♡3 小时前
JVM 频繁 Full GC 先看日志括号里的 cause,jstat 的 FGC 在 JDK 8 上会骗人
java·jvm
IT研究室3 小时前
最新计算机毕业设计选题推荐-基于spring boot的葡萄酒文化宣传网站-网站-文档指导-Java-springboot
java·spring boot·课程设计
慧都小项3 小时前
MyEclipse 中创建和测试 JAX-RS REST 接口:从项目配置到 REST Explorer
java·restful·myeclipse·jax-rs
Wang's Blog3 小时前
Java 项目实战: 交友项目 - 入门案例:编写 Dubbo 服务提供者
java·dubbo·交友
墨云安全3 小时前
内网资产无法暴露到公网,怎么做安全检测?2026 年内网渗透测试的落地路径
开发语言·安全·网络安全·渗透测试·php