买车票的例子



This content originally appeared on DEV Community and was authored by Liu yu

学了Runnable之后随手写的一个买车票的例子


package com.process;

class MyRunnable implements Runnable{

    private int ticket=10;
    private final String name;

    public MyRunnable(String name) {
        this.name = name;
    }


    @Override
    public void run()
    {
        while(ticket>0)
        {
            System.out.println(name + "买了第" + ticket-- + "张票");
        }
    }
}


public class TestSleep 
{
    public static void main(String[] args) 
    {
        MyRunnable r1=new MyRunnable("小明");
        MyRunnable r2=new MyRunnable("老师");


        Thread t1=new Thread(r1);
        Thread t2=new Thread(r2);

        t1.start();
        t2.start();

    }
}

运行之后的结果

老师买了第10张票
老师买了第9张票
老师买了第8张票
老师买了第7张票
老师买了第6张票
老师买了第5张票
老师买了第4张票
老师买了第3张票
老师买了第2张票
老师买了第1张票
小明买了第10张票
小明买了第9张票
小明买了第8张票
小明买了第7张票
小明买了第6张票
小明买了第5张票
小明买了第4张票
小明买了第3张票
小明买了第2张票
小明买了第1张票

发现有点问题,我需要他们共享10张票,而不是分别各自买10张票(这样没有实际意义)。

突然我想到了用static关键字的用法

private static int ticket = 10; // 添加 static
  • 作用:使变量属于类而非实例
  • 特点
    • 所有实例共享同一个变量
    • 内存中只存在一份拷贝
    • 可以通过类名直接访问(MyRunnable.ticket
  • 当前代码问题
  MyRunnable r1 = new MyRunnable("小明");
  MyRunnable r2 = new MyRunnable("老师");

每个实例有自己的 ticket 副本(各卖10张票),这不是真正的共享资源

修改方案:实现票源共享

方案A:使用 static 共享票源(不推荐)

class MyRunnable implements Runnable {
    private static int ticket = 10; // 静态共享
    private final String name;

    // ... 其他不变 ...
}

效果

  • 所有线程共享同一个 ticket
  • 输出可能:
  小明买了第10张票
  老师买了第9张票
  小明买了第8张票
  ...

问题

  • 需要同步机制(如 synchronized)避免线程安全问题
  • 全局状态难以管理
  • 不符合面向对象封装原则

方案B:共享实例(推荐)

public class TestSleep {
    public static void main(String[] args) {
        MyRunnable shared = new MyRunnable("售票点");

        Thread t1 = new Thread(shared, "小明");
        Thread t2 = new Thread(shared, "老师");

        t1.start();
        t2.start();
    }
}

修改点

  1. 创建 单个 MyRunnable 实例
  2. 通过 Thread 的第二个参数设置线程名称
  3. 修改 run() 方法:
   public void run() {
       while (ticket > 0) {
           // 获取当前线程名称
           String buyer = Thread.currentThread().getName();
           System.out.println(buyer + "买了第" + ticket-- + "张票");
       }
   }

最佳实践方案(完整代码)

class MyRunnable implements Runnable {
    private int ticket = 10; // 实例变量(多个线程共享同一实例时自然共享)

    @Override
    public void run() {
        while (true) {
            synchronized (this) { // 添加同步锁
                if (ticket <= 0) break;

                String buyer = Thread.currentThread().getName();
                System.out.println(buyer + "买了第" + ticket-- + "张票");
            }

            try {
                Thread.sleep(100); // 模拟处理时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class TestSleep {
    public static void main(String[] args) {
        MyRunnable shared = new MyRunnable();

        new Thread(shared, "小明").start();
        new Thread(shared, "老师").start();
    }
}

关键改进

  1. 使用 单个Runnable实例 实现资源自然共享
  2. 通过 synchronized 保证线程安全
  3. 使用 Thread.currentThread().getName() 获取线程名称
  4. 添加 Thread.sleep() 模拟真实场景
  5. 移除非必要的 name 字段(用线程名替代)

输出示例

小明买了第10张票
老师买了第9张票
小明买了第8张票
老师买了第7张票
...
小明买了第1张票

总结理解

  1. final:用于创建”常量”(不可变变量),保证安全性和代码意图清晰

    • 例:private final String ID = "A100";
  2. static:用于创建类级别共享资源

    • 例:public static final Logger LOG = Logger.getGlobal();
    • 常与 final 组合定义全局常量:public static final double PI = 3.14159;
  3. 多线程资源共享原则:

    • 优先使用 实例共享 而非 static 变量
    • 共享资源必须添加 同步机制
    • final 变量天然线程安全,无需同步

附:static vs final 对比表

特性 static final
作用对象 类级别 变量/方法/类级别
内存 类加载时初始化,仅一份 与普通变量相同
修改性 可修改(需同步) 初始化后不可修改
共享性 所有实例共享 每个实例独立
典型用途 全局配置、常量池、工具方法 不可变参数、常量定义
线程安全 需要额外同步 本身提供安全发布


This content originally appeared on DEV Community and was authored by Liu yu