Multi-threaded shared resource operations

Project requirements

      Recently, I am working on a payment-related project, because the upstream channel has an upper limit on the transaction limit of each merchant. Therefore, we need to implement a merchant polling mechanism to increase the transaction limit and meet transaction needs by using multiple merchant numbers.

demand analysis

      Through demand analysis, we know that the merchant's transaction quota is a shared resource, so it involves the synchronization of shared resources. We need to control the merchant's transaction quota, switch the submitted transaction merchant number in time, and ensure that it runs in multi-threaded conditions.

Function realization

Don't say so much, just paste the code directly.

Design interfaces according to business requirements:

public interface IncOrDecInterface {
    /**
     * Restoration quota
     * When the transaction fails, restore the merchant's available transaction limit.
     * @param amount
     */
    boolean inc(Long amount);

    /**
     * Reduce the amount
     * When transacting, the merchant's available quota will be reduced according to the transaction amount.
     * @param amount
     */
    boolean dec(Long amount);

    /**
     * Update the merchant's available quota
     * Since the merchant quota is updated once a day, this interface is designed to reset or update the merchant's available quota.
     * @param amount
     */
    boolean update(Long amount);

    /**
     * Determine whether the object has a lock
     */
    boolean isLook();
}

business object

public class Merchant implements IncOrDecInterface{
    private static final Logger log = LoggerFactory.getLogger(UnsPayMerchant.class);
    
    private String merchantNo;
    private String signKey;
    private volatile long maxTransactionAmount;

    private Lock lock = new ReentrantLock();
    private boolean isLook = false;

    public String getMerchantNo() {
        return merchantNo;
    }

    public void setMerchantNo(String merchantNo) {
        this.merchantNo = merchantNo;
    }

    public String getSignKey() {
        return signKey;
    }

    public void setSignKey(String signKey) {
        this.signKey = signKey;
    }

    public  long getMaxTransactionAmount() {
        try {
            lock.lock();
            return maxTransactionAmount;
        }finally {
            lock.unlock();
        }
    }

    public void setMaxTransactionAmount(long maxTransactionAmount) {
        this.maxTransactionAmount = maxTransactionAmount;
    }

    @Override
    public boolean inc(Long amount) {
        try{
            lock.lock();
            isLook = true;
            log.info("Merchant number [{}] current limit [{}] recharge amount [{}]",this.merchantNo,this.maxTransactionAmount,amount);
            this.maxTransactionAmount =this.maxTransactionAmount + amount;
            Thread.sleep(1000);
            return true;
        } catch (InterruptedException e) {
            e.printStackTrace ();
        } finally {
            lock.unlock();
            isLook = false;
        }
        return false;
    }

    @Override
    public boolean dec(Long amount) {
        try{
            lock.lock();
            isLook = true;
            log.info("Current thread [{}] Merchant ID [{}] Current amount [{}] Consumption amount [{}]",Thread.currentThread().getName(),this.merchantNo ,this.maxTransactionAmount, amount );
            if(this.maxTransactionAmount >= amount){
                this.maxTransactionAmount = this.maxTransactionAmount - amount;
                return true;
            }
        }  finally {
            lock.unlock();
            isLook = false;
        }
        return false;
    }

    @Override
    public boolean update(Long amount) {
        try{
            lock.lock();
            isLook = true;
            log.info("Merchant number[{}] current quota[{}] recovery quota[{}]", this.merchantNo, this.maxTransactionAmount, amount);
            this.maxTransactionAmount = amount;
            return true;
        }finally {
            lock.unlock();
            isLook = false;
        }
    }

    @Override
    public boolean isLook() {
        return isLook;
    }
}

Merchant container

public class MerchantContainer {
    private static final Logger log = LoggerFactory.getLogger(MerchantContainer.class);
    private static ConcurrentLinkedQueue<UnsPayMerchant> unsPayMerchants = new ConcurrentLinkedQueue<>();
    /**
     * Get business information
     * @param amount
     * @return
     */
    public static UnsPayMerchant getMerchant(Long amount) {
        //Determine whether there are merchants with available quota
        while(unsPayMerchants.size() > 0) {
            for (UnsPayMerchant unsPayMerchant : unsPayMerchants) {
                if(unsPayMerchant.isLook()){
                    continue;
                }else{
                    if(unsPayMerchant.getMaxTransactionAmount() >= amount){
                        unsPayMerchant.dec(amount);
                    }else{
                        unsPayMerchants.remove(unsPayMerchant);
                        continue;
                    }
                }
                return unsPayMerchant;
            }
        }
        return null;
    }

    /**
     * The merchant's available quota will be restored if the transaction fails
     *
     * @param unsPayMerchantFailure
     * @param amount
     * @return
     */
    public static boolean payFailure(UnsPayMerchant unsPayMerchantFailure, Long amount) {
        for (UnsPayMerchant unsPayMerchant : unsPayMerchants) {
            if (unsPayMerchantFailure.getMerchantNo().equals(unsPayMerchant.getMerchantNo())) {
                if(unsPayMerchant.isLook()){
                    continue;
                }else {
                    unsPayMerchant.inc(amount);
                }
                return true;
            } else {
                unsPayMerchants.add(unsPayMerchantFailure);
            }
        }
        return false;
    }

    /**
     * Update merchant quota
     *
     * @param merchant
     */
    public static void updateMaxAmount(UnsPayMerchant merchant) {
        for (UnsPayMerchant unsPayMerchant : unsPayMerchants) {
            if (unsPayMerchant.getMerchantNo().equals(merchant.getMerchantNo())) {
                unsPayMerchant.update(merchant.getMaxTransactionAmount());
                return;
            }
        }
        log.info("Add a merchant [{}] to the merchant pool with a limit of [{}] The current merchant pool has [{}] merchants.", merchant.getMerchantNo(), merchant.getMaxTransactionAmount(), unsPayMerchants.size() );
        unsPayMerchants.add(merchant);
    }

test class

public class UnspayMerchantLoopTest {

    private static Thread[] threads = new Thread[8];

    /**
     * Pre-execute tasks to prepare usable business data.
     */
    @Before
    public void init(){
        final UnsPayMerchant unsPayMerchant = new UnsPayMerchant();
        unsPayMerchant.setMerchantNo("101");
        unsPayMerchant.setMaxTransactionAmount(10000);
        UnsPayMerchant unsPayMerchant1 = new UnsPayMerchant();
        unsPayMerchant1.setMerchantNo("102");
        unsPayMerchant1.setMaxTransactionAmount(10000);
        UnsPayMerchant unsPayMerchant2 = new UnsPayMerchant();
        unsPayMerchant2.setMerchantNo("103");
        unsPayMerchant2.setMaxTransactionAmount(10000);

        MerchantContainer.updateMaxAmount(unsPayMerchant);
        MerchantContainer.updateMaxAmount(unsPayMerchant1);
        MerchantContainer.updateMaxAmount(unsPayMerchant2);
    }

    @Test
    public void merLoop() {
        doMerchantPay ();
        doPayFailuer();
        doReloadMerchant();
        for (Thread thread : threads) {
            try {
                thread.join();//Wait for the thread to finish executing
            } catch (InterruptedException e) {
                e.printStackTrace ();
            }
        }
    }

    /**
     * Simulate merchant transactions
     * Start 5 threads and sleep for 6 seconds each time.
     */
    private void doMerchantPay(){
        for (int i = 0; i < 5; i++) {
            Thread upLoadThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    Random random = new Random();
                    int maxAmount = 1000;
                    int minAmount = 0;
                    while (true) {
                        UnsPayMerchant merchant = MerchantContainer.getMerchant((long) random.nextInt(maxAmount) % (maxAmount - minAmount + 1) + minAmount);
                        if (merchant == null) {
                            System.out.println(Thread.currentThread().getName() + "The quota is exhausted!");
                            break;
                        }
                        try {
                            Thread.sleep(6000);
                        } catch (InterruptedException e) {
                            e.printStackTrace ();
                        }
                    }

                }
            });
            upLoadThread.start();
            threads[i] = upLoadThread;
        }
    }

    /**
     * When the simulated transaction fails, the merchant quota will be returned.
     * Start 2 threads and sleep for 5 seconds each time.
     */
    private void doPayFailuer(){
        for (int i = 5; i < 7; i++) {
            Thread upLoadThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    String[] strings = new String[]{"101", "102", "103"};
                    Random random = new Random();
                    int maxAmount = 2000;
                    int minAmount = 0;
                    while (true) {
                        for (int i = 0; i < 3; i++) {
                            UnsPayMerchant unsPayMerchant3 = new UnsPayMerchant();
                            unsPayMerchant3.setMerchantNo(strings[i]);
                            MerchantContainer.payFailure(unsPayMerchant3, (long) random.nextInt(maxAmount) % (maxAmount - minAmount + 1) + minAmount);
                        }
                        try {
                            Thread.sleep(5000);
                        } catch (InterruptedException e) {
                            e.printStackTrace ();
                        }
                    }

                }
            });
            upLoadThread.start();
            threads[i] = upLoadThread;
        }
    }

    /**
     * Restore the merchant's available quota
     * It can be used for merchants to restore the quota function every day.
     * Single thread, execute every 20 seconds.
     */
    private void doReloadMerchant(){
        Thread upLoadThread = new Thread(new Runnable() {
            @Override
            public void run() {
                String[] strings = new String[]{"101", "102", "103"};
                Random random = new Random();
                int maxAmount = 2000;
                int minAmount = 0;
                while (true) {
                    for (int i = 0; i < 3; i++) {
                        UnsPayMerchant unsPayMerchant3 = new UnsPayMerchant();
                        unsPayMerchant3.setMerchantNo(strings[i]);
                        unsPayMerchant3.setMaxTransactionAmount(1000);
                        MerchantContainer.updateMaxAmount(unsPayMerchant3);
                    }
                    try {
                        Thread.sleep(20000);
                    } catch (InterruptedException e) {
                        e.printStackTrace ();
                    }
                }

            }
        });
        upLoadThread.start();
        threads[7] = upLoadThread;
    }
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324954333&siteId=291194637
Recommended