integer对象加锁,导致失败

一、背景代码

public class SynchronizedTest {

    public static void main(String[] args) {
        Thread why = new Thread(new TicketConsumer(10), "why");
        Thread mx = new Thread(new TicketConsumer(10), "mx");
        why.start();
        mx.start();
    }
}

class TicketConsumer implements Runnable {

    private volatile static Integer ticket;

    public TicketConsumer(int ticket) {
        this.ticket = ticket;
    }

    @Override
    public void run() {
        while (true) {
            System.out.println(Thread.currentThread().getName() + "开始抢第" + ticket + "张票,对象加锁之前:" + System.identityHashCode(ticket));
            synchronized (ticket) {
                System.out.println(Thread.currentThread().getName() + "抢到第" + ticket + "张票,成功锁到的对象:" + System.identityHashCode(ticket));
                if (ticket > 0) {
                    try {
                        //模拟抢票延迟
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "抢到了第" + ticket-- + "张票,票数减一");
                } else {
                    return;
                }
            }
        }
    }
} 

二、分析原因
这段代码确实存在多个问题,主要集中在并发操作和同步机制方面。让我们逐一分析这些问题:

  1. 锁对象的变化问题
    由于 ticket 是一个 Integer 对象,而 Integer 在进行自增或自减操作后,会生成一个新的对象。因此,使用 synchronized(ticket) 锁定对象实际上锁定的是一个不断变化的对象,失去了同步的意义。
synchronized (ticket) {
    // 执行代码块
}

每次 ticket 的减一操作都会生成一个新的 Integer 对象,因此 synchronized 的锁对象在每次循环中可能都是不同的,这样会导致多个线程并发地访问这块代码,无法达到同步的目的。

  1. 静态变量的实例化问题
    在 TicketConsumer 构造函数中,你将 ticket 赋值为构造参数,但由于 ticket 是一个静态变量,所有实例共享同一个 ticket。这意味着在你创建多个 TicketConsumer 实例时,最后一个创建的实例会覆盖之前所有实例的 ticket 值。

  2. volatile 修饰的问题
    volatile 只能保证变量的可见性,即当一个线程修改了这个变量,其他线程可以立即看到它的变化,但不能保证复合操作(如自减操作)的原子性。这意味着在 ticket-- 操作中,多个线程可能会看到相同的 ticket 值,从而导致重复处理的问题。

代码修正
为了修正上述问题,可以采取以下措施:

使用一个单独的锁对象来同步,而不是使用 ticket。
确保 ticket 的原子性操作。
以下是修改后的代码:

import java.util.concurrent.TimeUnit;

public class SynchronizedTest {

    public static void main(String[] args) {
        TicketConsumer ticketConsumer = new TicketConsumer(10); // 共享同一个 TicketConsumer 实例
        Thread why = new Thread(ticketConsumer, "why");
        Thread mx = new Thread(ticketConsumer, "mx");
        why.start();
        mx.start();
    }
}

class TicketConsumer implements Runnable {

    private static Integer ticket;
    private static final Object lock = new Object(); // 单独的锁对象

    public TicketConsumer(int ticket) {
        TicketConsumer.ticket = ticket;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + "开始抢第" + ticket + "张票,对象加锁之前:" + System.identityHashCode(lock));
                
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "抢到第" + ticket + "张票,成功锁到的对象:" + System.identityHashCode(lock));
                    try {
                        // 模拟抢票延迟
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "抢到了第" + ticket-- + "张票,票数减一");
                } else {
                    return;
                }
            }
        }
    }
}

修正后的解释
锁对象:

定义了一个单独的 lock 对象,确保所有线程都在同一个对象上进行同步。
共享 TicketConsumer 实例:

两个线程共享同一个 TicketConsumer 实例,确保它们都操作同一个 ticket。
同步:

使用 synchronized (lock) 确保多个线程在操作 ticket 时是互斥的,防止出现并发问题。
通过这些修改,可以确保在多线程环境下对 ticket 的操作是线程安全的,并避免了前述的问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值