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

本文探讨了Java中的线程同步问题,通过示例代码展示了当多个线程操作同一对象时可能出现的数据不一致现象。通过使用`synchronized`关键字解决了基本的线程同步问题,但引入了死锁的风险。当两个线程分别持有对方需要的锁时,就会发生死锁,导致程序卡住。解决死锁需要避免资源的循环等待,并注意加锁的粒度。在实际开发中,简单的架构和单线程方案有时可能是更好的选择,尽管可能会牺牲一些性能,但可以降低错误和复杂性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程同步问题

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

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. 认知上的普及:开发一个框架一定要足够简单!!才能有更广的适用性,因为你不可能要求所有开发人员都会跨线程。因此在开发游戏服务器的时候一般都使用单线程。

单线程方案

  • 单线程?!不会慢么?!
  • 解决慢,要比教会所有人跨线程写逻辑简单
  • 架构师的工作是取舍和折中,不要炫技;
  • 项目框架越简单,适用性就越广
  • 可以出错,但不要让错误向下传递;
在Python中,可以使用线程同步锁(Thread Lock)来保证线程之间的互斥访问共享资源。线程同步锁可以防止线程同时修改共享变量,从而避免数据不一致的问题。 Python提供了threading模块来支持多线程编程,并且提供了Lock类作为线程同步锁的实现。下面是一个简单的示例代码: ```python import threading # 创建一个锁对象 lock = threading.Lock() # 共享变量 counter = 0 def increment(): global counter for _ in range(100000): # 获取锁 lock.acquire() try: counter += 1 finally: # 释放锁 lock.release() # 创建线程并启动 threads = [] for _ in range(10): t = threading.Thread(target=increment) threads.append(t) t.start() # 等待所有线程执行完毕 for t in threads: t.join() # 打印结果 print("Counter:", counter) ``` 在上述示例中,我们使用了一个全局变量`counter`作为共享资源,并创建了一个锁对象`lock`。在`increment`函数中,我们首先通过`lock.acquire()`获取锁,然后在临界区内对`counter`进行操作,最后通过`lock.release()`释放锁。这样,每次只有一个线程能够获取到锁,从而保证了共享资源的安全访问。 需要注意的是,获取锁后一定要在`finally`块中释放锁,以确保无论是否发生异常,都能够正确地释放锁。 线程同步锁是一种简单有效的线程同步机制,但在Python中还有其他更高级的同步原语,如条件变量、信号量等,可以根据具体的需求选择合适的同步机制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值