Java中使用synchronizedg关键字解决抢票超卖问题

一、多线程问题

我们先来看下在没有synchronized使用的情况下,如果不加锁机制,多线程卖票会出现什么问题?

package com.joshua317;

public class Ticket extends Thread{
    private static int ticketNums = 100;
    public static void main(String[] args) {
        Ticket ticket1 = new Ticket();
        Ticket ticket2 = new Ticket();
        Ticket ticket3 = new Ticket();

        ticket1.start();
        ticket2.start();
        ticket3.start();
    }

    @Override
    public void run() {
        while (true) {
            if (ticketNums <=0) {
                System.out.println("票已经售罄!");
                break;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("售票窗口:" + Thread.currentThread().getName() + " 售出一张票," + "剩余票数为:" + (--ticketNums));
        }
    }
}

 

 

 

上面例子我们可以发现,当多线程运行时,会导致出现负数的情况,也就是卖超了。原因就是:线程不同步。同一进程的多个线程Thread-0、Thread-1、Thread-2共享同一片存储空间,它们同时看见票仅剩一张,它们同时去抢,当某个线程抢到时,其他线程已经过了if判断票数的代码,于是最后一张被抢走时,票已经变为-1或者-2。

接下来我们通过创建线程的俩种方式,来使用synchronized关键字,看下synchronized的用法

二、使用synchronized同步代码块解决线程加锁

2.1 通过继承Thread来创建线程,使用synchronized

package com.joshua317;

public class Ticket extends Thread{
    private static int ticketNums = 100;
    public static void main(String[] args) {
        Ticket ticket1 = new Ticket();
        Ticket ticket2 = new Ticket();
        Ticket ticket3 = new Ticket();

        ticket1.start();
        ticket2.start();
        ticket3.start();
    }

    @Override
    public void run() {
        while (true) {
            //方法中的代码块,使用synchronized,注意此时同步锁锁定的对象是Ticket.class
            synchronized (Ticket.class) {
                if (ticketNums <=0) {
                    System.out.println("票已经售罄!");
                    break;
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("售票窗口:" + Thread.currentThread().getName() + " 售出一张票," + "剩余票数为:" + (--ticketNums));
            }
        }
    }
}

 

 

 

2.2 通过实现 Runnable 接口来创建线程,使用synchronized

package com.joshua317;

public class TicketRun implements Runnable{
    private static int ticketNums = 100;
    public static void main(String[] args) {
        TicketRun ticket = new TicketRun();

        Thread thread1 = new Thread(ticket);
        Thread thread2 = new Thread(ticket);
        Thread thread3 = new Thread(ticket);

        thread1.start();
        thread2.start();
        thread3.start();
    }

    @Override
    public void run() {
        while (true) {
            //方法中的代码块,使用synchronized,注意此时同步锁锁定的对象是this
            synchronized (this) {
                if (ticketNums <=0) {
                    System.out.println("票已经售罄!");
                    break;
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("售票窗口:" + Thread.currentThread().getName() + " 售出一张票," + "剩余票数为:" + (--ticketNums));
            }
        }
    }
}

 

 

 

三、Java synchronized关键字

在Java中,同步代码块被synchronized关键字标记。Java中的同步块是在某个对象上同步。synchronized 关键字声明的方法或者代码块同一时间只能被一个线程访问,也就是说同一时间所有的在同一个对象上同步的同步块只能被一个线程进入执行。所有其他尝试进入同步块的线程都将被阻塞直到在同步块内执行的线程退出这个同步块。

缺点就是:使用synichronized 将会影响效率。

3.1 代码块使用synchronized

synchronized(Obj){}

Obj 称为同步监视器,它可以是任何对象,但一般是共享资源。

同步方法中无需指定同步监视器,因为同步方法的同步监视器就是这个对象本身或者是class。

【例如 有class A,在class A中有同步方法,而在A的同步方法是中的 this 就是同步监视器】

同步监视器执行过程:

第一个线程访问,锁定同步监视器,执行{ }中的代码 第二个线程访问,发现同步监视器被锁定,无法访问 第一个线程执行结束,解锁同步监视器 第二个线程访问,同步监视器未锁,然后锁定并执行{ }其代码

3.2 类方法使用synchronized

public synchronized void methordName(){}

注意methordName()方法声明中使用synchronized关键字告诉 Java 该方法是同步的。

Java 中的同步实例方法在拥有该方法的实例(对象)上进行同步。因此,每个实例对象的实例方法将在不同对象(拥有实例方法的实例对象本身)上进行同步。每个实例只有一个线程可以在同步实例方法中执行。如果存在多个线程,则每一个实例对象,同一时刻只有一个线程可以进入其同步实例方法中执行。即每个实例一个线程。

 

posted @ 2022-01-28 18:20  joshua317  阅读(595)  评论(0编辑  收藏  举报