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

相关推荐
橘子汽水1684 分钟前
Leetcode15:三数之和
java·数据结构·算法
Nolla5 分钟前
为什么 assertThrows 需要 () ->:Lambda 延迟执行与异常捕获
java
SL_staff8 分钟前
风控规则不该写代码:一个开发者视角的规则引擎实践拆解
java·架构·全栈
catchadmin10 分钟前
免费可商用 PHP 管理后台 CatchAdmin V5.4.0 发布,新增短信服务能力
开发语言·php
小白学大数据21 分钟前
API 调用错误处理实战:分层定位、有纪律地重试与可观测性设计
开发语言·网络·人工智能
xieliyu.32 分钟前
计算机网络|数据链路层详解:MAC 地址、交换机、ARP 协议、CRC 校验
java·网络·笔记·计算机网络·java-ee
SimonKing36 分钟前
手机投屏到电脑,不用装任何 App,这个开源工具免费搞定:QtScrcpy
java·后端·程序员
学长毕业设计44 分钟前
基于SpringBoot的奶茶店服务管理系统的设计与实现(源码+文档+讲解视频)
java·spring boot·后端
蛋先生DX1 小时前
明明都是源码到CPU,各语言中间原理咋不是一个套路?
java·javascript·go