多线程经典案例&线程安全问题

207 阅读2分钟

多线程经典案例:多线程售票

我们定义一个用于售票的线程类,充当售票员的角色

  • 定义一个静态变量充当公共票池,并记录票的数量
  • 在run方法中实现卖票的功能逻辑

卖票线程类:

public class TicketThread extends Thread{
    //公共票池票数
    private static int ticketNum = 100;

    @Override
    public void run() {
        while (ticketNum>0){
            if(ticketNum>0){
                System.out.println(Thread.currentThread().getName() + "卖出票号" + ticketNum);
                ticketNum--;
            }
        }
    }
}

创建多个线程卖票:

public static void main(String[] args) {
    TicketThread t1 = new TicketThread();
    TicketThread t2 = new TicketThread();
    TicketThread t3 = new TicketThread();
    t1.setName("售票员1");
    t2.setName("售票员2");
    t3.setName("售票员3");
    t1.start();
    t2.start();
    t3.start();
}

结果:

image.png

我们运行多次会发现,有不同线程重复卖同一张票的情况,为什么会出现这种情况,因为产生了线程安全问题


多线程-线程安全问题

线程安全问题:多个线程访问同一个数据,容易出现线程安全问题

java中解决线程安全问题的方式:

  • 同步
  • 加锁

同步方式

java中同步的关键字 synchronized ,可以修饰代码块和方法

同步代码块方式:

  • 使用同步代码块需要有一个监听对象
  • 保证所有的线程共享一个同步监听对象的,也就是保证被同步监听对象是被所有线程共享的,如:静态属性,字节码文件对象等
public class TicketThread extends Thread{
    //公共票池票数
    private static int ticketNum = 100;

    @Override
    public void run() {
        while (ticketNum>0){
            //同步代码块
            synchronized (TicketThread.class){
                if(ticketNum>0){
                    System.out.println(Thread.currentThread().getName() + "卖出票号" + ticketNum);
                    ticketNum--;
                }
            }
        }
    }
}

同步方法方式:

  • 就是在需要被同步的方法上面加关键字 synchronized
  • 加的位置:在返回值类型的前面
  • 不需要也不能够显示的写同步监听对象
  • 如果是一个非static的方法,那么同步监听对象就是this
  • 如果是static修饰的方法,那么同步监听对象就是当前方法所在的类的字节码对象
public class TicketThread extends Thread{
    //公共票池票数
    private static int ticketNum = 100;

    @Override
    public void run() {
        while (ticketNum>0){
            test();
        }
    }
    /**
     * 同步方法
     */
    static synchronized void test(){
        if(ticketNum>0){
            System.out.println(Thread.currentThread().getName() + "卖出票号" + ticketNum);
            ticketNum--;
        }
    }
}

加锁方式

  • 创建一个Lock接口的实现类对象(锁对象)
  • 在可能发生线程安全的核心代码加锁lock()方法
  • 使用try...catch...finally代码块,finally中解锁unlock()方法

注: 加锁相对于同步而言灵活性更高,效率较高,同步由jvm维护,加锁代码级别的,需要手动释放

public class TicketThread extends Thread{
    //公共票池票数
    private static int ticketNum = 50;
    //创建锁对象,static修饰,所有卖票线程共享
    static Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (ticketNum>0){
            //加锁
            lock.lock();
            try {
                if(ticketNum>0){
                    System.out.println(Thread.currentThread().getName() + "卖出票号" + ticketNum);
                    ticketNum--;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                //解锁
                lock.unlock();
            }
        }
    }
}