Byteman 让 i++ 百分百线程不安全
Byteman 让 i++ 百分百线程不安全
在我早期的文章当中,我使用过一个插件 vmlens 实现让 i++
展现了百分百的线程不安全。在演示示例中,使用了两个线程并发执行 i++
,然后就看到了线程不安全的全过程。
但是 vmlens 当时是个付费软件,作者给白嫖用户两周的体验期,虽然我我提了一个 BUG ,也没得到任何的优待。所以很快进行了简单的尝试之后,就放弃探索 vmlens 。
最近开始研究 Byteman
的官方文档过程中,当我看到了关于多线程管理的部分,原来可以控制多个故障的多线程同步,突然意识到有可能到了 vmlens
一样的套路。如果我们可以控制访问一个变量的线程访问(读/写)顺序,那我们应该可以很容易模仿出线程不安全的场景。
既然如此,那我将重现一下 i++
百分百线程不安全的远古神级。
i++ 为什么不安全
i++
是线程不安全的,因为它不是一个原子操作。i++
其实包含了三个步骤:
- 读取变量值:从内存中读取变量
i
的当前值。 - 自增操作:将读取的值加1。
- 写回变量值:将更新后的值存回内存中。
在单线程环境下,这个过程不会有问题,但在多线程环境中,如果多个线程同时执行 i++
,可能会发生竞态条件。例如,两个线程都读取了相同的初始值,但都还没来得及写回时,导致最终只会增加一次,而不是两次。
使用同步机制:可以通过使用 synchronized
关键字来确保每次只有一个线程能够访问这个变量进行 i++
操作。
synchronized(this) {
i++;
}
使用原子类:Java 提供了 AtomicInteger
来处理类似的操作,它保证了 i++
的原子性。
AtomicInteger i = new AtomicInteger(0);
i.incrementAndGet(); // 相当于 i++
这样可以避免多个线程同时修改变量时导致的不一致性。
测试代码
下面是我的测试代码,逻辑非常简单。代码创建了两个线程,每个线程每隔一秒对共享变量 i
进行递增操作,并输出当前值。
package com.;
public class FunTester {
static int i = 0;
public static void test() {
i++;
println(().getame() + " " + i);
}
public static void main(String[] args) {
for (int j = 0; j < 2; j++) {
new Thread(() -> {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
test();
}
}).start();
}
}
}
这个代码的逻辑可以简单梳理为以下几点:
- **静态变量
i
**:定义了一个静态变量i
,初始值为0
。这是所有线程共享的变量,用来记录每次的递增操作。 test()
方法:test()
方法的作用是对i
进行自增操作,然后输出当前线程的名字和自增后的值。在原代码中,i++
是线程不安全的,多个线程可能会在读取和写入i
时发生冲突。main()
方法:在main()
方法中,使用了一个循环创建了 两个线程,每个线程会进入一个无限循环(while (true)
)。每个线程在执行时,都会每隔 1秒(Thread.sleep(1000)
)调用一次test()
方法,执行自增操作,并输出线程名称和当前的i
值。- 多线程执行:两个线程同时运行,不断对
i
进行递增操作,由于i++
不是原子操作,线程可能会发生数据竞争,导致递增结果不正确(输出值可能不连续或重复)。
总结:
代码创建了两个线程,每个线程每隔一秒对共享变量 i
进行递增操作,并输出当前值。然而,由于 i++
操作不是线程安全的,程序可能出现竞态条件,导致输出结果不符合预期。
Byteman rule 脚本
下面是我的 Byteman 脚本的内容:
代码语言:javascript代码运行次数:0运行复制RULE sync test
CLASS com..FunTester
METHOD test
HELPER _mesh.byteman.helper.FunHelper
AT ETRY
IF TRUE
DO setThreadame()
EDRULE
RULE async test
CLASS com..FunTester
METHOD test
HELPER _mesh.byteman.helper.FunHelper
AT WRITE i
IF checkThreadame()
DO println(().getame() + " 持有锁");
EDRULE
这个 Byteman 脚本的作用是通过对类 com..FunTester
的 test
方法进行增强,借助 Byteman 的规则动态监控和修改线程执行时的行为。主要目标是通过 FunHelper
来记录和监控线程的名字,并在访问共享变量 i
时输出线程持有锁的信息。下面逐步解释每个规则的含义:
sync test
- RULE 名称:
sync test
,给这条规则命名为sync test
。 - CLASS:目标类是
com..FunTester
,这条规则作用于该类。 - METHOD:这条规则针对
test()
方法,表示要拦截这个方法。 - HELPER:指定了一个辅助类
_mesh.byteman.helper.FunHelper
,其中定义了一些辅助方法来支持规则逻辑。 - AT ETRY:该规则触发的时机是在
test()
方法的入口处,也就是方法一开始执行时。 - IF TRUE:条件始终为
TRUE
,意味着无条件执行。 - **DO setThreadame()**:在
test()
方法执行时,调用FunHelper
中的setThreadame()
方法。这通常是用于记录或设置当前线程的名称。
当 test()
方法执行时,无论什么情况下,都会调用 setThreadame()
,可能用于记录每个线程的名称,以便后续跟踪哪个线程正在执行。
async test
- RULE 名称:
async test
,命名为async test
。 - CLASS:同样作用于类
com..FunTester
。 - METHOD:针对
test()
方法。 - HELPER:依然是
_mesh.byteman.helper.FunHelper
,同样使用辅助类来提供额外功能。 - AT WRITE i:表示该规则在变量
i
被写入时触发。也就是说,当i
的值发生改变时,规则会被执行(对应于i++
时的写操作)。 - **IF checkThreadame()**:该规则只有在辅助类中的
checkThreadame()
返回true
时才会触发。这个方法可能会根据线程名称来判断当前线程是否符合某种条件。 - **DO println(().getame() + " 持有锁")**:如果条件为
true
,则会打印当前线程的名字,并显示"持有锁"
,表示当前线程正在执行对共享变量i
的修改操作。
当 test()
方法中的共享变量 i
被写入时(即 i++
发生时),Byteman 会检查当前线程的名字。如果线程名字满足 checkThreadame()
的条件,就会输出该线程已经持有锁的信息。这可以用于调试或监控,查看哪个线程正在修改共享变量 i
,避免竞态条件。
通过这两个脚本,我们就可以在 i++
赋值的过程中,第一个线程等待第二个线程进来,就能模式两个线程同时完成 i+
计算,然后在分别开始执行赋值过程。我自己的思路就是在赋值之前做一个阻塞的设置,当一个线程到达,必须等另外一个线程过去,然后自己再执行。这个设计基本上可以保障后来的进程先赋值,因为我再等待的方法中加上了神迹 Thread.sleep(10);
。
实践效果
下面是注入 Byteman 脚本前后,控制台输出日志变化情况:
代码语言:javascript代码运行次数:0运行复制Thread- 7
Thread- 8
Thread-2 9
Thread- 10
Thread-2 11
setThreadame Thread-2
setThreadame Thread-2
Thread- 持有锁
Thread- 12
setThreadame Thread-
Thread-2 持有锁
Thread-2 12
setThreadame Thread-2
Thread- 持有锁
Thread- 1
setThreadame Thread-
Thread-2 持有锁
Thread-2 1
setThreadame Thread-2
可以看出,注入前,看着似乎是线程安全的,但是注入之后,每个线程输出的值都是一样的,百分百线程不安全了。
关于 FunHelper
这里实现比较简单,而且粗糙,目前各种实践中积累一些好的设计和场景。打算从 Byteman 源码中再汲取一些营养。后面等我感觉代码成熟了,再来分享一篇文章。有兴趣的可以加好友一起交流一下 Byteman 相关技术话题。
本文参与 腾讯云自媒体同步曝光计划,分享自。原始发表:2024-12-02,如有侵权请联系 cloudcommunity@tencent 删除变量多线程脚本线程安全#感谢您对电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格的认可,转载请说明来源于"电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格
上一篇:Aeron 框架初探
下一篇:Scheduled线程池实践
推荐阅读
留言与评论(共有 16 条评论) |
本站网友 孔雀大卫城 | 6分钟前 发表 |
当 test() 方法中的共享变量 i 被写入时(即 i++ 发生时) | |
本站网友 信质电机 | 28分钟前 发表 |
多个线程可能会在读取和写入 i 时发生冲突 | |
本站网友 脑干 | 9分钟前 发表 |
HELPER:指定了一个辅助类 _mesh.byteman.helper.FunHelper | |
本站网友 stormii | 28分钟前 发表 |
每个线程每隔一秒对共享变量 i 进行递增操作 | |
本站网友 底部 | 12分钟前 发表 |
有兴趣的可以加好友一起交流一下 Byteman 相关技术话题 | |
本站网友 少时诵诗书 | 24分钟前 发表 |
然后在分别开始执行赋值过程 | |
本站网友 晨之科 | 4分钟前 发表 |
意味着无条件执行 | |
本站网友 感叹号的作用 | 3分钟前 发表 |
以便后续跟踪哪个线程正在执行 | |
本站网友 芭乐的功效与作用 | 27分钟前 发表 |
用来记录每次的递增操作 | |
本站网友 孕周计算器 | 10分钟前 发表 |
代码语言:javascript代码运行次数:0运行复制AtomicInteger i = new AtomicInteger(0); i.incrementAndGet(); // 相当于 i++ 这样可以避免多个线程同时修改变量时导致的不一致性 | |
本站网友 新再见艳阳天 | 6分钟前 发表 |
也就是方法一开始执行时 | |
本站网友 怎么去掉老年斑 | 21分钟前 发表 |
初始值为 0 | |
本站网友 短租平台哪个最好 | 17分钟前 发表 |
使用了一个循环创建了 两个线程 | |
本站网友 天津星美国际影城 | 14分钟前 发表 |
这个设计基本上可以保障后来的进程先赋值 | |
本站网友 我好色 | 15分钟前 发表 |
再来分享一篇文章 |