线程实现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接口

相关推荐
invicinble5 分钟前
对tomcat的提供的功能与底层拓扑结构与实现机制的理解
java·tomcat
较真的菜鸟19 分钟前
使用ASM和agent监控属性变化
java
黎雁·泠崖26 分钟前
【魔法森林冒险】5/14 Allen类(三):任务进度与状态管理
java·开发语言
2301_763472461 小时前
C++20概念(Concepts)入门指南
开发语言·c++·算法
TechWJ2 小时前
PyPTO编程范式深度解读:让NPU开发像写Python一样简单
开发语言·python·cann·pypto
qq_12498707532 小时前
基于SSM的动物保护系统的设计与实现(源码+论文+部署+安装)
java·数据库·spring boot·毕业设计·ssm·计算机毕业设计
Coder_Boy_2 小时前
基于SpringAI的在线考试系统-考试系统开发流程案例
java·数据库·人工智能·spring boot·后端
Mr_sun.2 小时前
Day06——权限认证-项目集成
java
瑶山2 小时前
Spring Cloud微服务搭建四、集成RocketMQ消息队列
java·spring cloud·微服务·rocketmq·dashboard
abluckyboy2 小时前
Java 实现求 n 的 n^n 次方的最后一位数字
java·python·算法