解决线程不安全问题的几种方式

发布于:2024-07-08 ⋅ 阅读:(22) ⋅ 点赞:(0)

线程不安全问题

日常生活中我们会经常碰到在不同的平台上买各种票的问题,例如在App、线下售票窗口等购买火车票、电影票。这里面就存在着线程安全的问题,因为当多个线程访问同一个资源时,会导致数据出错,例如甲和乙两人同时看中了一张票,甲先点击购买,此时线程直接转到乙去执行,乙点击购买,并锁定票,但甲还不知道此时就会发生数据错误。
在这里插入图片描述
为了避免发生这样的错误,我们通过两种方式来解决线程不安全的问题。

使用同步代码块

  1. 格式:
synchronized(任意对象){
      线程可能出现不安全的代码
}
  1. 任意对象:就是我们的锁对象

  2. 执行: 一个线程拿到锁之后,会进入到同步代码块中执行,在此期间,其他线程拿不到锁,就进不去同步代码块,需要在同步代码块外面等待排队。等着执行的线程执行完毕,出了同步代码块,相当于释放锁了,等待的线程才能抢到锁,才能进入到同步代码块中执行。

public class MyTicket implements Runnable{
    int ticket = 100;//定义100张票
    Object obj = new Object();//任意new一个对象
    @Override
    public void run() {
        while(true){
            synchronized (obj){
                if (ticket>0){
                    System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
                    ticket--;
                }
            }
        }
    }
}
public class Test {
    public static void main(String[] args) {
        MyTicket myTicket = new MyTicket();
        Thread t1 = new Thread(myTicket, "张三");
        Thread t2 = new Thread(myTicket, "李四");
        t1.start();
        t2.start();
    }
}

没有再出现抢票的情况。
在这里插入图片描述

使用同步方法

同步方法有两类,一种是非静态的,一种是静态的。

非静态同步方法

格式:

  修饰符 synchronized 返回值类型 方法名(参数){
      方法体
      return 结果
  }

可以通过同步代码块的方式了解到默认锁是this

public class MyTicket implements Runnable{
    int ticket = 100;//定义100张票
    @Override
    public void run() {
        while(true){
           method01();
           method02()
        }
    }
   	public synchronized void method(){
        if (ticket>0){
            System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
            ticket--;
        }
    }
    public void method02(){
       synchronized(this){
           System.out.println(this+"..........");
           if (ticket>0){
               System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
               ticket--;
           }
       }
   }
}

静态同步方法

格式:

修饰符 static synchronized 返回值类型 方法名(参数){
      方法体
      return 结果
  }

可以通过同步代码块的方式了解到默认锁是class对象

public class MyTicket implements Runnable{
    static int ticket = 100;//定义100张票
    @Override
    public void run() {
           	method01();
           	method02();
        }
    }

    public static synchronized void method01(){
        if (ticket>0){
            System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
            ticket--;
        }
    }
    public static void method02(){
        synchronized(MyTicket.class){
            if (ticket>0){
                System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
                ticket--;
            }
        }
    }
}

使用Lock锁

Lock是一个接口,实现类是ReentrantLock。
通过两个方法:Lock() 获取锁、unlock() 释放锁

public class MyTicket implements Runnable{
    int ticket = 100;//定义100张票
    Lock lock = new ReentrantLock(); //创建Lock对象
    @Override
    public void run() {
        while(true){
            try {
                
                lock.lock(); //获取锁
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
                    ticket--;
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }finally {
                lock.unlock();//释放锁
            }
        }
    }
}
public class Test {
    public static void main(String[] args) {
        MyTicket myTicket = new MyTicket();
        Thread t1 = new Thread(myTicket, "张三");
        Thread t2 = new Thread(myTicket, "李四");
        t1.start();
        t2.start();
    }
}

通过synchronized实现同步代码块或方法和通过Lock解决线程不安全的区别

synchronized不管是同步代码块还是同步方法,都需要在结束一对{}之后,释放锁对象。而Lock是通过两个方法控制需要被同步的代码,更灵活。