线程同步问题
当多个线程操作同一个对象的时候,就有可能出现数据不一致的问题。
运行如下代码
public class TestUser {
// 角色血量
public int currHp;
}
public class TestMain {
public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
System.out.println("第 "+i+" 次测试");
new TestMain().test1();
}
}
private void test1(){
TestUser newUser = new TestUser();
newUser.currHp = 100;
Thread t0 = new Thread(()->{newUser.currHp = newUser.currHp -1;});
Thread t1 = new Thread(()->{newUser.currHp = newUser.currHp -1;});
t0.start();
t1.start();
try {
t0.join();
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
if(newUser.currHp != 98){
throw new RuntimeException("当前血量错误,currHp = " + newUser.currHp);
}else{
System.out.println("当前的血量正确");
}
}
}
运行结果
期望结果:
一开始currHp=100,两个线程都对其进行减一操作,结果应该为98。但是进过测试发现有时候会是99,与期望结果不同。
原因:
简单来说就是线程读取到100后还没来得及写入,就被另一个线程读取了。所以两个线程读取到的都是100,写入之后也就99了。这就是线程不同步问题。
加锁解决线程同步问题
在TestUser类中添加一个同步方法用来
public class TestUser {
public int currHp;
synchronized public void subHp(int val){
if(val <= 0){
return;
}
this.currHp = this.currHp - val;
}
}
测试类通过调用同步方法来实现
public class TestMain {
public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
System.out.println("第 "+i+" 次测试");
new TestMain().test2();
}
}
private void test2(){
TestUser newUser = new TestUser();
newUser.currHp = 100;
Thread t0 = new Thread(()->{newUser.subHp(1);});
Thread t1 = new Thread(()->{newUser.subHp(1);});
t0.start();
t1.start();
try {
t0.join();
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
if(newUser.currHp != 98){
throw new RuntimeException("当前血量错误,currHp = " + newUser.currHp);
}else{
System.out.println("当前的血量正确");
}
}
}
测试结果
看来加上synchronized
是解决了线程同步问题。
但是能彻底解决吗???
加锁带来的死锁问题
public class TestUser {
public int currHp;
// 减血
synchronized public void subHp(int val){
if(val <= 0){
return;
}
this.currHp = this.currHp - val;
}
// 攻击角色,让角色减血
synchronized public void attackUser(TestUser targetUser){
final int dmgPoint = 10;
targetUser.subHp(dmgPoint);
}
}
public class TestMain {
public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
System.out.println("第 "+i+" 次测试");
new TestMain().test3();
}
}
private void test3(){
TestUser user1 = new TestUser();
user1.currHp = 100;
TestUser user2 = new TestUser();
user2.currHp = 100;
Thread t0 = new Thread(()->{user1.attackUser(user2);});
Thread t1 = new Thread(()->{user2.attackUser(user1);});
t0.start();
t1.start();
try {
t0.join();
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果: 可以看到程序已经卡住了
如何查看死锁呢?通过命令行
jps
jstack 进程id
上图可以看出两个线程都BLOCKED
了并且都在等待subHp()
在游戏的开发中使用synchronized
关键字是很容易导致死锁的。
加锁的缺点
- 使用
synchronized
关键字其实已经侵入到业务逻辑了,一旦出现业务上的错误的时候,无法理清楚是锁导致业务上的错误,还是业务上的错误导致锁不对了。简单来说就是无法定位问题 - 认知上的普及:开发一个框架一定要足够简单!!才能有更广的适用性,因为你不可能要求所有开发人员都会跨线程。因此在开发游戏服务器的时候一般都使用单线程。
单线程方案
- 单线程?!不会慢么?!
- 解决慢,要比教会所有人跨线程写逻辑简单
- 架构师的工作是取舍和折中,不要炫技;
- 项目框架越简单,适用性就越广
- 可以出错,但不要让错误向下传递;