Summary of Java Concurrent Programming Practice

premise

First, the scene is a hotel-based business.I simplified my business for friends to read easily. Business: After opening a room, a bill will be added, and a room schedule record will be added. The room schedule is mainly for the time used in the room to be non-conflicting.For example: Bill A, use Room 1, use period of time is 220-06-01 12:00-2020-06-02 12:00, then the period of time that room 1 is also needed to open a room cannot conflict with the period of Bill A.

Business Class

For simplicity, I have simplified several entity classes.

Bill Class

public class Bill {
    // Bill Number
    private String serial;

    // Room schedule id
    private Integer room_schedule_id;
    // ...get set
}

Room Class

// Room Class
public class Room {
    private Integer id;

    // Room name
    private String name;
    // get set...
}

Room Scheduling Class

import java.sql.Timestamp;

public class RoomSchedule {
    private Integer id;
    
    // Room id
    private Integer roomId;

    // start time
    private Timestamp startTime;

    // End time
    private Timestamp endTime;
    // ...get set
}

actual combat

And of course, there is no shortage of Jmeter pressure measuring tools, portal: https://jmeter.apache.org/download_jmeter.cgi
To avoid some small partners not accessing the official website, I uploaded to Baidu cloud: Link: https://pan.baidu.com/s/1c9l3Ri0KzkdIkef8qtKZeA Extraction Code: kjh6

sychronized

For the first time, I thought about the sychronized keyword.No way, the base is poor.The code is as follows:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;

import java.sql.Timestamp;

/**
 * Opening business class
 */
@Service
public class OpenRoomService {
    @Autowired
    DataSourceTransactionManager dataSourceTransactionManager;
    @Autowired
    TransactionDefinition transactionDefinition;

    public void openRoom(Integer roomId, Timestamp startTime, Timestamp endTime) {
        // Open Transaction
        TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
        try {
            synchronized (RoomSchedule.class) {
                if (isConflict(roomId, startTime, endTime)) {
                    // throw exception
                }
                // Add room schedule...
                // Add Bill

                // Submit Transaction
                dataSourceTransactionManager.commit(transaction);
            }
        } catch (Exception e) {
            // Rollback transaction
            dataSourceTransactionManager.rollback(transaction);
            throw e;
        }
    }

    public boolean isConflict(Integer roomId, Timestamp startTime, Timestamp endTime) {
        // Determine if there is a conflict between room schedules...
    }
}
  1. sychronized(RoomSchedule.class), equivalent to the open business are serial.Whether it's room 1 or room 2.You need to wait for the previous thread to finish the open business before you can execute it.That's not good.
  2. Transactions must be committed in the synchronization code block sychronized, which is required.Otherwise, when thread A opens with room 1, the synchronization code block is executed, the transaction has not been committed, and thread B finds that room scheduling in room 1 does not conflict, then it is problematic.

Error point: Some friends might think it's all serial execution. Why not write the synchronized keyword on the method? First, the openRoom method is non-static, then synchronized locks on this object.The @Service annotation class in Spring is multiple, so you cannot add the synchronized keyword to a method.

Second improvement (wait-notification mechanism)

Because in the example above, the opening operation is serial.In fact, room 1 and room 2 should be available in parallel.Can we use synchronized (Room instance)?The answer is No.
stay Chapter III Solving Atomic Problems Among them, I mentioned that using locks must be immutable objects. If a mutable object is used as a lock, it is equivalent to changing locks when the mutable object is modified. Locks here refer to synchronized locked objects, that is, Room instances.Room instances cannot be synchronized because they are mutable objects (the set method modifies the attribute values of the instances to make them mutable).
In this improvement, I used Chapter V Waiting-Notification Mechanism , I added the RoomAllocator Room Resource Allocator, which requires the lock resource to be acquired in the RoomAllocator when the room opens, and the thread goes into wait() waiting state if the acquisition fails.NotryAll() wakes up all waiting threads when the thread releases the lock resource.
The RoomAllocator room resource allocator code is as follows:

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

/**
 * Room Resource Allocator (Singleton Class)
 */
public class RoomAllocator {
    private final static RoomAllocator instance = new RoomAllocator();

    private final List<Integer> lock = new ArrayList<>();

    private RoomAllocator() {}

    /**
     * Get Lock Resource
     */
    public synchronized void lock(Integer roomId) throws InterruptedException {
        // Is there a thread already occupying the room's resources
        while (lock.contains(roomId)) {
            // Thread Waiting
            wait();
        }

        lock.add(roomId);
    }

    /**
     * Release Lock Resource
     */
    public synchronized void unlock(Integer roomId) {
        lock.remove(roomId);
        // Wake up all threads
        notifyAll();
    }

    public static RoomAllocator getInstance() {
        return instance;
    }
}

The opening business only needs to modify the openRoom method as follows:

    public void openRoom(Integer roomId, Timestamp startTime, Timestamp endTime) throws InterruptedException {
        RoomAllocator roomAllocator = RoomAllocator.getInstance();
        // Open Transaction
        TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
        try {
            roomAllocator.lock(roomId);
            if (isConflict(roomId, startTime, endTime)) {
                // throw exception
            }
            // Add room schedule...
            // Add Bill

            // Submit Transaction
            dataSourceTransactionManager.commit(transaction);
        } catch (Exception e) {
            // Rollback transaction
            dataSourceTransactionManager.rollback(transaction);
            throw e;
        } finally {
            roomAllocator.unlock(roomId);
        }
    }

After this modification, opening rooms with room 1 and room 2 can be executed in parallel.

summary

The above example may have a better way to solve it, but my strength does not allow me to do so....This example was also created by myself working on a project.After all, there is no practical experience, only theory, not enough to learn concurrency well.I hope you can also do something [laugh] in your project, but of course you can't fool around.
Follow-up if concurrency is used in other scenarios, will continue to write and practice articles oh~

Personal Blog URL: https://colablog.cn/

If my article helps you, you can follow my WeChat Public Number and share it with you for the first time

Tags: Programming Java SQL Apache JDBC

Posted on Fri, 05 Jun 2020 22:12:46 -0400 by isaacsf