java干货 线程安全问题以及synchronized实现减库存

一、线程安全问题

1.1为什么出现线程安全问题?

  • 线程安全问题是指会造成数据不一致。比如说商品库存就100,结果卖出了101份甚至更多。
  • 在单线程下对共享资源(如静态变量)的修改操作,不会引发安全问题。
  • 在多线程环境下,读操作不会有安全问题,写操作会造成安全问题,因为在执行时不是原子操作
  • 原子操作:是指一种不可分割的操作,cpu执行一次就能完成,即操作在执行过程中不会被其他线程中断。

二、使用sychronized关键字实现同步减库存

2.1 不加同步锁

class TicketThread implements Runnable{
    private static int ticketCount = 8;
    Object o = new Object();
    @Override
    public void run(){
        while (true){

                if(ticketCount > 0){
                    System.out.println(Thread.currentThread().getName() + "售出第 " + ticketCount + " 张票");
                    ticketCount--;
                } else {
                    break;
                }
            }
    }
}
public class Demo07 {
    public static void main(String[] args) {
        TicketThread ticketThread = new TicketThread();
        Thread thread1 = new Thread(ticketThread);
        thread1.setName("窗口1");

        TicketThread ticketThread2 = new TicketThread();
        Thread thread2 = new Thread(ticketThread2);
        thread2.setName("窗口2");

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

窗口1售出第 8 张票
窗口1售出第 7 张票
窗口2售出第 8 张票
窗口1售出第 6 张票
窗口2售出第 5 张票
窗口1售出第 4 张票
窗口2售出第 3 张票
窗口1售出第 2 张票
窗口2售出第 1 张票

分析:一共八张票,结果第八张重复售出两次,存在线程安全问题。

2.2 加synchronized同步代码块

  • 格式
 synchronized (锁对象){
            //处理逻辑
        }
  • 锁对象必须是引用类型,如String、Integer、Object、Class等
  • 必须是同一个锁对象,哪个线程获取到锁对象,就可以执行该代码块
  • 案例代码:
class TicketThread implements Runnable{
    private static int ticketCount = 8;
    Object o = new Object();
    @Override
    public void run(){
        while (true){
                synchronized (TicketThread.class){
                    if(ticketCount > 0){
                        System.out.println(Thread.currentThread().getName() + "售出第 " + ticketCount + " 张票");
                        ticketCount--;
                    } else {
                        break;
                    }
                }
            }
    }
}
public class Demo07 {
    public static void main(String[] args) {
        TicketThread ticketThread = new TicketThread();
        Thread thread1 = new Thread(ticketThread);
        thread1.setName("窗口1");

        TicketThread ticketThread2 = new TicketThread();
        Thread thread2 = new Thread(ticketThread2);
        thread2.setName("窗口2");

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

窗口1售出第 8 张票
窗口1售出第 7 张票
窗口1售出第 6 张票
窗口1售出第 5 张票
窗口1售出第 4 张票
窗口1售出第 3 张票
窗口1售出第 2 张票
窗口1售出第 1 张票

分析:

  • 正常售出,没有超卖
  • 使用Class 类对象作为锁对象最保险,因为该对象始终只有一个,例如TicketThread.class
  • 也可以使用类的静态变量(引用类型)作为锁对象,也最保险,因为该对象始终只有一个
  • 其次使用this,前提是该类只有一个实例。比如通过 new TicketThread()实例化出了多个对象,那么就是多个this、多把锁,不安全。实例变量也是这个情况。在本例中,有两个TicketThread()对象,若使用this和实例变量作为锁对象,则出现线程安全问题

2.3 同步方法

2.3.1 同步实例方法
class TicketThread implements Runnable{
    private static int ticketCount = 8;
    Object o = new Object();
    @Override
    public void run(){
       sellTicket();
    }
    public synchronized void sellTicket(){ // 锁对象是this
        while (true){
                if(ticketCount > 0){
                    System.out.println(Thread.currentThread().getName() + "售出第 " + ticketCount + " 张票");
                    ticketCount--;
                } else {
                    break;
                }
            }
        }
    }
}
public class Demo07 {
    public static void main(String[] args) {
        TicketThread ticketThread = new TicketThread();
        Thread thread1 = new Thread(ticketThread);
        thread1.setName("窗口1");

        Thread thread2 = new Thread(ticketThread);
        thread2.setName("窗口2");

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

窗口1售出第 8 张票
窗口1售出第 7 张票
窗口1售出第 6 张票
窗口1售出第 5 张票
窗口1售出第 4 张票
窗口1售出第 3 张票
窗口1售出第 2 张票
窗口1售出第 1 张票

分析:

  • synchronized修饰实例方法,锁对象是this,确保只有一个实例对象,因此new TicketThread()了一个对象
  • 若 Thread thread2 = new Thread(ticketThread);修改为Thread thread2 = new Thread(new TicketThread());则出现安全问题,两个对象的this不是同一个实例对象,也就是不是同一把锁,出现超卖
窗口2售出第 8 张票
窗口2售出第 7 张票
窗口2售出第 6 张票
窗口2售出第 5 张票
窗口2售出第 4 张票
窗口1售出第 8 张票
窗口2售出第 3 张票
窗口1售出第 2 张票
窗口2售出第 1 张票

2.3.2 同步静态方法
class TicketThread implements Runnable{
    private static int ticketCount = 8;
    Object o = new Object();
    @Override
    public void run(){
       sellTicketStatic();
    }

    public synchronized static void sellTicketStatic(){ // 锁对象是TicketThread.class
        while (true){
            synchronized (TicketThread.class){
                if(ticketCount > 0){
                    System.out.println(Thread.currentThread().getName() + "售出第 " + ticketCount + " 张票");
                    ticketCount--;
                } else {
                    break;
                }
            }
        }
    }
}
public class Demo07 {
    public static void main(String[] args) {
        TicketThread ticketThread = new TicketThread();
        Thread thread1 = new Thread(ticketThread);
        thread1.setName("窗口1");

        TicketThread ticketThread2 = new TicketThread();
        Thread thread2 = new Thread(ticketThread2);
        thread2.setName("窗口2");

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

窗口1售出第 8 张票
窗口1售出第 7 张票
窗口1售出第 6 张票
窗口1售出第 5 张票
窗口1售出第 4 张票
窗口1售出第 3 张票
窗口1售出第 2 张票
窗口1售出第 1 张票

分析:

  • synchronized修饰类静态方法,锁对象是TicketThread.class,只有一把锁,因此是安全的

2.4 总结

请添加图片描述

  • 29
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值