实现5个抢物品,为什么只有张三一个人抢,问题在哪


package com.syh.thread;
//2.实现:创建一个线程安全的秒杀网站购物程序,实现购物。
//a)还有20个物品,5个人秒杀。 
//b)设置延迟是:0.5s
//c)注意实现:线程安全,不允许出现0个-1的情况。

public class Instantshoppingxcan implements Runnable {
    private int num = 20;
    boolean flag = true;

    @Override
    public void run() {
        method();
    }
    private void method() {
        synchronized (this) {
            while (flag) {
                if (num < 1) {
                    flag = false;
                }
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {

                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "个物品");
                num--;
            }
            
        }

    }

}
package com.syh.thread;

public class InstantshoppingApp {

    public static void main(String[] args) {
        //创建真是角色
        Instantshoppingxcan instantshopping =new Instantshoppingxcan();
        //创建代理角色+引用
        Thread t1 =new Thread(instantshopping,"张三");
        Thread t2 =new Thread(instantshopping,"李狗");
        Thread t3 =new Thread(instantshopping,"王五");
        Thread t4 =new Thread(instantshopping,"张三丰");
        Thread t5 =new Thread(instantshopping,"李易峰");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
        

    }

}

引用chatgpt内容作答:
问题出在method()方法内的逻辑以及synchronized的使用上。在当前代码中,所有的线程共享同一个Instantshoppingxcan实例,而且在synchronized块内的逻辑并没有正确处理线程安全的问题。

具体问题分析如下:

1、共享资源num: 所有线程共享了同一个num变量,代表剩余的物品数量。但是synchronized块中只检查了num是否小于1,而没有检查是否等于0。这就导致了多个线程可以同时判断num大于0,从而都进入抢购逻辑,导致抢到多个相同物品。

2、synchronized的范围: synchronized块的范围应该包括整个抢购逻辑,即从判断num是否大于0到更新num的操作。然而,当前的synchronized块只包裹了一个小部分代码,使得多个线程可以同时进入synchronized块之外的逻辑,造成线程安全问题。

为了解决这些问题,可以对代码进行如下修改:

public class Instantshoppingxcan implements Runnable {
    private int num = 20; // 初始物品数量
    boolean flag = true;

    @Override
    public void run() {
        method();
    }

    private synchronized void method() {
        while (flag && num > 0) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            if (num > 0) {
                System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "个物品");
                num--;
            }
            
            if (num == 0) {
                flag = false;
            }
        }
    }
}

在修改后的代码中,method()方法使用了synchronized来保证在抢购逻辑中的所有操作都是线程安全的。每个线程都会检查num是否大于0,然后进行抢购和更新num的操作。当num等于0时,flag会被设置为false,表示抢购结束,从而保证只有一个线程可以执行结束后的操作。

这样,多个线程就可以正确地抢购物品了。

flag 变量定义成volatile 类型的试试。

问题出现在以下代码片段:

//创建真是角色
Instantshoppingxcan instantshopping = new Instantshoppingxcan();
//...
Thread t1 = new Thread(instantshopping, "张三");
Thread t2 = new Thread(instantshopping, "李狗");
Thread t3 = new Thread(instantshopping, "王五");
Thread t4 = new Thread(instantshopping, "张三丰");
Thread t5 = new Thread(instantshopping, "李易峰");


由于所有线程都共享同一个 Instantshoppingxcan 对象,所以他们都在争抢同一个锁对象,并且在一个线程进行抢购时,其他线程会等待释放锁。这就导致了只有一个线程能够抢到物品。

写个循环,启动线程,每个人都会去抢购物品,

for (int i = 1; i <= 5; i++) {
           …………
            thread.start();
        }

你的代码中存在一个问题,导致只有张三一个人能够抢到物品。问题出在 flag 的控制上。

你在 method() 方法中的 synchronized 块内使用了一个 while (flag) 循环来控制购物的进行,但是在循环内部没有适当的改变 flag 的状态。因此,一旦一个线程进入循环,其他线程由于 flag 仍然为 true 而无法进入循环,只能等待。

为了解决这个问题,你可以将 flag 的状态检查和更改移到 synchronized 块内,并且使用一个条件来通知其他线程。这样,当一个线程抢到物品后,它会修改 flag,然后通知其他线程。

以下是修正后的代码示例:

public class Instantshoppingxcan implements Runnable {
    private int num = 20;
    private boolean flag = true;

    @Override
    public void run() {
        method();
    }

    private void method() {
        synchronized (this) {
            while (num > 0) {  // 检查数量是否大于0
                if (!flag) {
                    break;  // 如果flag为false,结束循环
                }
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "个物品");
                num--;

                if (num == 0) {
                    flag = false;  // 当物品数量为0时,将flag置为false
                }
            }
        }
    }
}

这样,每个线程在抢到物品后,会检查物品数量是否为0,如果是则将 flag 置为 false,其他线程会检测到 flagfalse 而结束循环。这样可以保证所有线程都能参与购物。

【以下回答由 GPT 生成】

问题出在程序中使用了对象锁synchronized(this),导致只有一个线程能够获取到对象锁,其他线程无法进入临界区。由于创建了多个Thread对象,但是这些Thread对象共享的是同一个Instantshoppingxcan对象,因此只有一个线程能够成功获取到对象锁,其他线程都会被阻塞。为了解决这个问题,可以将对象锁改为类锁,即将synchronized(this)改为synchronized(Instantshoppingxcan.class)。

修改后的代码如下所示:

package com.syh.thread;

public class Instantshoppingxcan implements Runnable {
    private static int num = 20;
    boolean flag = true;

    @Override
    public void run() {
        method();
    }

    private void method() {
        synchronized (Instantshoppingxcan.class) {
            while (flag) {
                if (num < 1) {
                    flag = false;
                }
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (num > 0) {
                    System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "个物品");
                    num--;
                }
            }
        }
    }
}

修改后的代码中,使用了类锁synchronized(Instantshoppingxcan.class),这样每个线程都会去竞争类锁,并无需等待其他线程释放对象锁。这样就能保证每个线程都有机会进入临界区,实现了多个人同时参与抢购的需求。

请注意,由于代码中有if (num < 1)条件判断,当num小于等于0时,线程会继续执行,但不再打印抢购信息。为了避免线程继续执行,在if (num < 1)后面加上break语句即可。



【相关推荐】



如果你已经解决了该问题, 非常希望你能够分享一下解决方案, 写成博客, 将相关链接放在评论区, 以帮助更多的人 ^-^

1.关于只有张三在抢的问题,应该与编译器优化和锁升级有关,使用synchronized 关键字,导致后续进程一直处于饥饿状态,无法获取到锁。如果希望这5个用户,按照申请锁的顺序来抢物品,可以考虑使用公平锁;

  1. 另外,num和flag最好加上volatile 修饰一下。

img

代码

package com.syh.thread;
//2.实现:创建一个线程安全的秒杀网站购物程序,实现购物。
//a)还有20个物品,5个人秒杀。 
//b)设置延迟是:0.5s
//c)注意实现:线程安全,不允许出现0个-1的情况。

public class Instantshoppingxcan implements Runnable {
    private int num = 20;
    private volatile boolean flag = true;

    @Override
    public void run() {
        while (flag) {
            if (num > 0) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {

                    e.printStackTrace();
                }
                synchronized (this) {
                    if (num > 0) {
                        System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "个物品");
                        num--;
                    }
                    if (num <= 0) {
                        flag = false;
                    }
                    notifyAll();
                }
            }
        }

    }

}
package com.syh.thread;

public class InstantshoppingApp {

    public static void main(String[] args) {
        //创建真实角色
        Instantshoppingxcan instantshopping =new Instantshoppingxcan();
        //创建代理角色+引用
        Thread t1 =new Thread(instantshopping,"张三");
        Thread t2 =new Thread(instantshopping,"李狗");
        Thread t3 =new Thread(instantshopping,"王五");
        Thread t4 =new Thread(instantshopping,"张三丰");
        Thread t5 =new Thread(instantshopping,"李易峰");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
        

    }

}


共享资源的锁定机制有问题。
由于synchronized锁定的是this对象,因此只有一个线程能够获取到锁,并执行抢物品的操作。其他线程会被阻塞,直到获取到锁。
要实现多个人同时抢物品,可以将锁定的对象改为一个共享的对象,而不是this。

public class Instantshoppingxcan implements Runnable {
    private int num = 20;
    boolean flag = true;
    private static final Object lock = new Object(); // 共享的锁定对象

    @Override
    public void run() {
        method();
    }

    private void method() {
        synchronized (lock) { // 使用共享的锁定对象
            while (flag) {
                if (num < 1) {
                    flag = false;
                }
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "个物品");
                num--;
            }
        }
    }
}

问题在于所有的线程都共享了同一个Instantshoppingxcan实例作为共享资源,而在你的method()方法中使用了synchronized (this)来同步。这就导致了只有一个线程能够进入临界区,而其他线程被阻塞在外部,无法执行抢购的逻辑。

为了实现多个人同时抢购物品,你应该对共享资源(即物品数量)进行同步。可以通过在方法签名中添加synchronized关键字来实现方法级别的同步,也可以使用对象级别的锁来实现。下面是你可以使用的两种方法:

  1. 使用方法级别的同步:
public synchronized void method() {
    while (flag && num > 0) {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "个物品");
        num--;
    }
}
  1. 使用对象级别的锁:
public class Instantshoppingxcan implements Runnable {
    private int num = 20;
    boolean flag = true;
    private final Object lock = new Object();

    @Override
    public void run() {
        method();
    }

    private void method() {
        synchronized (lock) {
            while (flag && num > 0) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "个物品");
                num--;
            }
        }
    }
}

在上述代码中,我们将synchronized关键字放在method()方法内部,或者使用一个私有的对象lock来进行同步。这样每个线程都会在进入临界区之前获取锁,从而实现多个线程同时抢购物品的效果。

所有的线程都共享同一个 Instantshoppingxcan 实例,并且都尝试在 num 小于1时抢购物品。由于在 synchronized 块内部只有一个物品减少,而所有的线程都试图进入这个同步块,所以最终只有一个线程能够抢到物品。
要解决这个问题,可以使用一个原子变量来确保线程安全地减少物品数量。此外,当物品数量为0时,需要阻止线程继续运行。

参考newbing

package com.syh.thread;

public class InstantShopping implements Runnable {
    private int num = 20;
    private boolean flag = true;

    @Override
    public void run() {
        method();
    }

    private synchronized void method() {
        while (flag) {
            if (num > 0) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "个物品");
                num--;
                notifyAll(); // 唤醒其他等待的线程
            } else {
                flag = false;
            }
        }
    }
}

package com.syh.thread;

public class InstantShoppingApp {

    public static void main(String[] args) {
        // 创建真实角色
        InstantShopping instantShopping = new InstantShopping();
        // 创建代理角色+引用
        Thread t1 = new Thread(instantShopping, "张三");
        Thread t2 = new Thread(instantShopping, "李狗");
        Thread t3 = new Thread(instantShopping, "王五");
        Thread t4 = new Thread(instantShopping, "张三丰");
        Thread t5 = new Thread(instantShopping, "李易峰");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}