模拟售票中 synchronized 没有防止超卖问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/white_ice/article/details/81777658

       最近在学习 《JAVA多线程编程实战指南》这本书,学到内部锁 synchronized 这里,自己就编写Demo演示模拟售票中超卖的线程安全问题,首先我的代码如下:

package com.sailing.thread.entity;

import com.sailing.thread.main.ThreadMain;

/**
 * @author Baibing
 * @project: java_thread
 * @package: com.sailing.thread.entity
 * @Description: java 创建线程之继承 Thread 方式
 * @date 2018/8/17 09:37
 */
public class DemoThread extends Thread{


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

    /**
     * 模拟售票,多线程下会出现线程安全问题,会发生超卖
     */
    public void sellTicket1(){
        if(ThreadMain.num > 0){
            ThreadMain.num--;
            System.out.println(Thread.currentThread().getName() + "抢票成功");
        }else{
            System.out.println(Thread.currentThread().getName()+"票已经售罄,不好意思");
        }
    }

    /**
     * 方法上增加java 内部锁(synchronized)
     */
    public synchronized void sellTicket2(){
        if(ThreadMain.num > 0){
            ThreadMain.num--;
            System.out.println(Thread.currentThread().getName() + "抢票成功");
        }else{
            System.out.println(Thread.currentThread().getName()+"票已经售罄,不好意思");
        }
    }
}

       启动类中创建了25个线程模拟用户抢票,总票数设置为20,正常情况应该有  5 个人抢不到票,代码如下:

package com.sailing.thread.main;

import com.sailing.thread.entity.DemoThread;

public class ThreadMain {

    //总票数为 20 张
    public static Integer num = 20;

    public static void main(String[] args) {

        //模拟 25 个人订票
        for (int i = 0; i < 25; i++){
            DemoThread thread = new DemoThread();
            thread.start();
        }

    }
}

       在 run() 方法中调用 sellTicket() 方法,这个因为没有加任何同步锁,多试几次肯定就会发生超卖,不过当我换成调用 sellTicket2() 方法时,我起初以为加了 synchronized 内部锁以后就会变成线程安全的,不会发生超卖现象,没想到啪啪啪的打脸 0.0 ,以下是试了好几次之后的结果,我们发现只有4个人没有抢到票,证明有一个人买到了票,还是发生了超卖。

      于是乎我查找相关资料,发现 synchronized 是对 类的当前实例 进行加锁,当有一个线程正在访问一个实例的 synchronized 修饰的方法时,别的线程是无法访问这个实例的其他的 synchronized 方法,毕竟一个对象只有一把锁,但是其它线程可以访问此实例的其他的非 synchronized 方法,但是如果 线程A访问实例 Obj1 的synchronized 方法,线程B访问另一个实例 obj2 的 synchonized 方法,如果它们操作的共享变量,那么线程安全就无法保证了,这也就是我们上面虽然加了同步锁,依然出现超卖的原因,解决这种方法是将 synchornized 作用于 static 静态方法,这样的话,对象锁就是当前类对象,由于无论创建多少个实例对象,但对于的类对象拥有只有一个,所有在这样的情况下对象锁就是唯一的:

package com.sailing.thread.entity;

import com.sailing.thread.main.ThreadMain;

/**
 * @author Baibing
 * @project: java_thread
 * @package: com.sailing.thread.entity
 * @Description: java 创建线程之继承 Thread 方式
 * @date 2018/8/17 09:37
 */
public class DemoThread extends Thread{


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

    /**
     * 模拟售票,多线程下会出现线程安全问题,会发生超卖
     */
    public void sellTicket1(){
        if(ThreadMain.num > 0){
            ThreadMain.num--;
            System.out.println(Thread.currentThread().getName() + "抢票成功");
        }else{
            System.out.println(Thread.currentThread().getName()+"票已经售罄,不好意思");
        }
    }

    /**
     * 方法上增加java 内部锁(synchronized),这种方式是给当前实例对象加锁
     */
    public synchronized void sellTicket2(){
        if(ThreadMain.num > 0){
            ThreadMain.num--;
            System.out.println(Thread.currentThread().getName() + "抢票成功");
        }else{
            System.out.println(Thread.currentThread().getName()+"票已经售罄,不好意思");
        }
    }

    /**
     * 方法上增加java 内部锁(synchronized),这种方式是给当前类对象加锁
     */
    public static synchronized void selTicket3(){
        if(ThreadMain.num > 0){
            ThreadMain.num--;
            System.out.println(Thread.currentThread().getName() + "抢票成功");
        }else{
            System.out.println(Thread.currentThread().getName()+"票已经售罄,不好意思");
        }
    }
}

       最后,当synchronized作用于静态方法时,其锁就是当前类的class对象锁。由于静态成员不专属于任何一个实例对象,是类成员,因此通过class对象锁可以控制静态成员的并发操作。需要注意的是如果一个线程A调用一个实例对象的非static synchronized方法,而线程B需要调用这个实例对象所属类的静态 synchronized方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的class对象,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。

猜你喜欢

转载自blog.csdn.net/white_ice/article/details/81777658
今日推荐