【Java多线程】线程同步问题,同步锁,死锁以及加锁的缺点

线程同步问题

当多个线程操作同一个对象的时候,就有可能出现数据不一致的问题。
运行如下代码

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关键字是很容易导致死锁的。

加锁的缺点

  1. 使用synchronized关键字其实已经侵入到业务逻辑了,一旦出现业务上的错误的时候,无法理清楚是锁导致业务上的错误,还是业务上的错误导致锁不对了。简单来说就是无法定位问题
  2. 认知上的普及:开发一个框架一定要足够简单!!才能有更广的适用性,因为你不可能要求所有开发人员都会跨线程。因此在开发游戏服务器的时候一般都使用单线程。

单线程方案

  • 单线程?!不会慢么?!
  • 解决慢,要比教会所有人跨线程写逻辑简单
  • 架构师的工作是取舍和折中,不要炫技;
  • 项目框架越简单,适用性就越广
  • 可以出错,但不要让错误向下传递;
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值