java 抢票软件 java抢票源码_java 抢票软件


一、不安全案例

多人抢票问题


package org.example.thread;

/**
 * 多人抢票问题
 * @author lzhiyun
 * @date 2020/6/28 7:44
 */
public class UnsafeThreadDemo {

    static class TicketThread implements Runnable {
        // 总票数
        private int ticketNumber = 10;

        // 标志位
        private boolean ticketFlag = true;

        @Override
        public void run() {
            while (ticketFlag) {
                buyTicket();
            }
        }

        private void buyTicket() {
            // 如果票数不够,则退出
            if (ticketNumber <= 0) {
                ticketFlag = false;
                return;
            }

            // 模拟网络延迟
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 票数足够,则购买
          System.out.println(Thread.currentThread().getName() + "购买了第" + (ticketNumber--) + "张票");
        }
    }

    public static void main(String[] args) {
        // 创建购票线程
        TicketThread ticketThread = new TicketThread();

        // 创建购票者
        new Thread(ticketThread, "甲").start();
        new Thread(ticketThread, "乙").start();
        new Thread(ticketThread, "丙").start();
        
        /* 输出结果,出现两次第7张票;出现了第0张票
         * 甲购买了第10张票
         * 乙购买了第8张票
         * 丙购买了第9张票
         * 乙购买了第6张票
         * 丙购买了第7张票
         * 甲购买了第7张票
         * 丙购买了第5张票
         * 甲购买了第3张票
         * 乙购买了第4张票
         * 丙购买了第2张票
         * 甲购买了第1张票
         * 乙购买了第0张票
         */
    }
}


银行取钱问题


package org.example.thread;

import lombok.Data;

/**
 * 银行取钱问题
 * @author lzhiyun
 * @date 2020/6/28 7:44
 */
public class UnsafeThreadDemo2 {

    // 账户
    @Data
    static class Account {
        // 账户余额
        private int money;

        // 账户名称
        private String name;

        public Account(int money, String name) {
            this.money = money;
            this.name = name;
        }
    }

    // 银行取钱线程
    static class BankThread extends Thread {
        // 账户信息
        private Account account;

        // 取钱数
        private int withdrawMoney;

        // 用户名
        private String name;

        public BankThread(String name, Account account, int withdrawMoney) {
            super(name);
            this.account = account;
            this.withdrawMoney = withdrawMoney;
        }

        @Override
        public void run() {
            // 判断是否能够取钱
            if (withdrawMoney > account.getMoney()) {
                return;
            }

            // 模拟网络延迟
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 返回结果
            account.setMoney(account.getMoney() - withdrawMoney);
            System.out.println(getName() + " 取走了 " + withdrawMoney);
            System.out.println(account.getName() + " 账户剩余 " + account.getMoney());
        }
    }

    public static void main(String[] args) {
        // 创建账户
        Account account = new Account(100, "工商银行");

        // 创建取钱用户
        new BankThread("张三", account, 50).start();
        new BankThread("李四", account, 100).start();

        /* 输出结果
         * 张三 取走了 50
         * 工商银行 账户剩余 50
         * 李四 取走了 100
         * 工商银行 账户剩余 -50
         */
    }
}


集合赋值问题


package org.example.thread;

import java.util.ArrayList;
import java.util.List;

/**
 * 集合赋值问题
 * @author lzhiyun
 * @date 2020/6/28 7:44
 */
public class UnsafeThreadDemo3 {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> {
                list.add("hello world");
            }).start();
        }
        System.out.println(list.size());

        /* 输出结果
         * 9994
         */
    }
}


二、synchronized

介绍

线程安全问题:多个线程操作同个共享变量时,会因为其他线程的干扰导致数据产生误差,就会出现线程安全问题。

synchronized,能够保证在同一时刻最多只有一个线程执行某个方法或代码块,保证并非的原子性、可见性和有序性。当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放即可。

问题

  • 一个线程持有锁会导致其他所有需要此锁的线程挂起;
  • 在多线程竞争下,加锁、释放锁会导致较多的上下文切换和调度延时,引起性能问题;
  • 如果一个优先级高的线程等待一个优先级低的线程,会导致优先级倒置,引起性能问题。

用法

  • synchronized普通同步方法
public synchronized void method(String[] args) {}


锁的是当前实例对象,进入同步代码前要获取当前实例的锁。

  • synchronized静态同步方法
public static synchronized void method(String[] args) {}


锁的是当前类的class对象,进入同步代码前要获得当前类对象的锁。

  • synchronized同步方法块
synchronized(Object) {...}


锁的是括号里的对象,进入同步代码前要获取给定对象的锁。

对象监视器

  • 同步代码块里的Object可以是任何对象,但是推荐使用共享资源作为对象监视器。
  • 同步方法无需指定对象监视器,因为同步方法的对象监视器就是this,表示这个实例对象,或者是class对象。
  • 对象监视器的执行过程:
  1. 第一个线程访问,锁定对象监视器,执行其中代码。
  2. 第二个线程访问,发现对象监视器被锁定,挂起无法访问。
  3. 第一个线程访问完毕,解锁对象监视器。
  4. 第二个线程访问,发现对象监视器没有锁,然后锁定并执行。

总结

  • 用域控制对”对象“的访问,每个对象对应一把锁,只有获得该锁才能执行,否则线程会阻塞。方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
  • 方法里面需要修改的内容才需要锁,查询的内容不需要,锁的太多,浪费资源,影响效率。

三、Lock

介绍

从JDK5开始,Java提供了通过显示定义同步锁对象来实现同步。

java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。

锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。

Lock和ReadWriteLock是两大锁的根接口,Lock代表实现类是ReentrantLock(可重入锁),ReadWriteLock(读写锁)的代表实现类是ReentrantReadWriteLock。

方法

  • lock(),该方法用于获取锁,如果锁已经被其他线程获取,则进行等待。
  • unLock(),该方法用域释放锁,一般将释放锁操作放在finally块中进行。
  • tryLock(),该方法用域获取锁并且有返回值,如果获取成功返回true,获取失败返回false。该方法无论如何都会立即返回,在拿不到锁时不会一直在那等待。
  • tryLock(long time, TimeUnit unit),与tryLock()方法是类似的,但是多了time(TimeUnit)的等待时间。时间范围内获取到锁则返回true,否则返回false。
Lock lock = new ReentranLock();
lock.lock();
try {
    // TODO
} catch(Exception e) {
    // TODO
} finally {
    lock.unlock(); // 释放锁
}


synchronized与Lock的对比

  • Lock是java.util.concurrent.locks包下的一个接口,synchronized是Java的一个关键字,属于内置的语言实现。
  • Lock是显式锁(手动获取和释放锁),synchronized是隐式锁,出了作用域自动释放。
  • Lock只有代码块锁,synchronized有代码块锁和方法锁。
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)。
  • 优先使用顺序:;
    Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步方法(在方法体之外)

三、死锁

案例


package org.example.thread;

/**
 * 死锁demo
 * @author lzhiyun
 * @date 2020/6/28 8:02
 */
public class DeadLock {
    public static void main(String[] args){
        // 共享资源obj1
        public static String obj1 = "obj1";
        // 共享资源obj2
        public static String obj2 = "obj2";
        
        // 执行线程LockA
        new Thread(new LockA()).start();
        // 执行线程LockB
        new Thread(new LockB()).start();
    }    
}

/**
 * 线程LockA,先锁住obj1,在锁住obj2
 */
class LockA implements Runnable{
    @Override
    public void run(){
        try{
            System.out.println("LockA running");
            while(true){
                synchronized(DeadLock.obj1){
                    System.out.println("LockA lock obj1");
                    Thread.sleep(2000);
                    synchronized(DeadLock.obj2){
                        System.out.println("LockA lock obj2");
                    }
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

/**
 * 线程LockB,先锁住obj2,在锁住obj1
 */
class LockB implements Runnable{
    @Override
    public void run(){
        try{
            System.out.println("LockB running");
            while(true){
                synchronized(DeadLock.obj2){
                    System.out.println("LockB lock obj2");
                    Thread.sleep(2000);
                    synchronized(DeadLock.obj1){
                        System.out.println("LockB lock obj1");
                    }
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}


四个必要条件

  1. 互斥条件:一个资源每次只能被一个进程使用;
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

注意:破坏其中的任意一个或者多个条件,就可以避免死锁的发生。