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();
}
}
修改点:
- 创建 单个
MyRunnable
实例 - 通过
Thread
的第二个参数设置线程名称 - 修改
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();
}
}
关键改进:
- 使用 单个Runnable实例 实现资源自然共享
- 通过
synchronized
保证线程安全 - 使用
Thread.currentThread().getName()
获取线程名称 - 添加
Thread.sleep()
模拟真实场景 - 移除非必要的
name
字段(用线程名替代)
输出示例:
小明买了第10张票
老师买了第9张票
小明买了第8张票
老师买了第7张票
...
小明买了第1张票
总结理解
-
final
:用于创建”常量”(不可变变量),保证安全性和代码意图清晰- 例:
private final String ID = "A100";
- 例:
-
static
:用于创建类级别共享资源- 例:
public static final Logger LOG = Logger.getGlobal();
- 常与
final
组合定义全局常量:public static final double PI = 3.14159;
- 例:
-
多线程资源共享原则:
- 优先使用 实例共享 而非
static
变量 - 共享资源必须添加 同步机制
-
final
变量天然线程安全,无需同步
- 优先使用 实例共享 而非
附:static
vs final
对比表
特性 | static |
final |
---|---|---|
作用对象 | 类级别 | 变量/方法/类级别 |
内存 | 类加载时初始化,仅一份 | 与普通变量相同 |
修改性 | 可修改(需同步) | 初始化后不可修改 |
共享性 | 所有实例共享 | 每个实例独立 |
典型用途 | 全局配置、常量池、工具方法 | 不可变参数、常量定义 |
线程安全 | 需要额外同步 | 本身提供安全发布 |
This content originally appeared on DEV Community and was authored by Liu yu