Concurrent programming - thread advanced (conductor story)

Posted by crob611 on Sat, 18 Sep 2021 18:17:53 +0200

2, Thread advanced (classic question: conductor ticket)

1. Thread implementation method

No more nonsense, code first

(1) Inherit Thread class

public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("inherit Thread Class mode");
    }
    
    public static void main(String args[]){
        MyThread myThread = new MyThread();
        myThread.start();
    }
}

This method inherits the Thread class and rewrites its run method. The Thread class is essentially an instance that implements the Runnable interface and represents an instance of a Thread. The start() method is a native method that starts a new Thread and executes the run() method.

(2) Implement Runnable interface

public class RunnableTest implements Runnable{
    public function run(){
        System.out.println("realization runnable Interface mode");
    }
}

public static void main(String[] args){
    RunnableTest runnabletest = new RunnableTest();
    Thread thread = new Thread(runnabletest);
    thread.start();
}

This method uses the parameterized constructor of Thread to initialize the instance object implementing runnable interface into Thread.

(3) Implement Callable interface

public class CallableTest implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int i = 0;
        System.out.println("realization runnable Interface mode");
        return i;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CallableTest callableTest = new CallableTest();
        FutureTask<Integer> futureTask = new FutureTask<Integer>(callableTest);
        Thread thread = new Thread(futureTask);
        thread.start();
        System.out.println("The return value is:" + futureTask.get());
    }
}

Through the running results, we can find that the call method implementing Callable override has a return value, and its return value can be other types. From this, we can know that it is more powerful than the above two methods.

This method is to override the call() method and declare the operation to be performed by this thread in call().
Create the object of the Callable interface implementation class, pass the object of the Callable interface implementation class as to the FutureTask constructor, and create the FutureTask object. Pass the FutureTask object as a parameter to the constructor of the Thread class to create a Thread object.

(4) Anonymous inner class implementation

public static void main(String[] args) {
        //1. Pass the subclass object of Runnable as a parameter to the construction method of Thread
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Anonymous inner class 1");
            }
        }).start();

        //Represents a subclass object of Thread. There is a start method in the Thread class, which can be called directly
        new Thread(){
            public void run(){
                System.out.println("Anonymous inner class 2");
            }
        }.start();
}

The implementation of anonymous inner class is consistent with methods 1 and 2 written above.

2. The story of the conductor

 An existing demand is to simulate the ticket selling mechanism in stations or cinemas. There are a certain number of tickets( n = 100),At the same time, there are two ticket sellers selling tickets at different windows. Can you simulate their ticket selling situation?

In the face of such a problem, we should first think that tickets are public and have a certain number. Two conductors need to sell tickets at the same time. How can we sell tickets at the same time and at random? And will not overbought?

We first learn to sell now, and use the randomness and concurrency of threads to solve this problem.

No more nonsense. Look at the code:

public class Ticket implements Runnable {
    // Number of votes
    public static int num = 100;

    public void run() {
        while (true) {
                if (num > 0) {
                    System.out.println(Thread.currentThread().getName() + "Sell one ticket and the rest" + num + "Zhang");
                    num--;
                } else {
                    break;
                }
        };
    }

    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        Thread thread1 = new Thread(ticket,"1");
        Thread thread2 = new Thread(ticket,"2");

        thread1.start();
        thread2.start();
    }
}

From the results, we can find that the result of selling the same ticket occurs. This is because the concurrency of threads leads to the result that one conductor sells the ticket but fails to reduce the number of tickets and buy it for another conductor, which leads to the result of selling the same ticket.

Facing the above situation, we can optimize by locking. synchronized ("")

Add synchronized ("") to the ticket selling action, i.e

while (true) {
    synchronized ("") {
        if (num > 0) {
            System.out.println(Thread.currentThread().getName() + "Sell one ticket and the rest" + num + "Zhang");
            num--;
        } else {
            break;
        }
    }
}

Operation results:

We find that the above results are not particularly scattered. We can add a waiting time to the ticket selling action

public class Ticket implements Runnable {
    // Number of votes
    public static int num = 100;

    Lock lock = new ReentrantLock();
    public void run() {
        while (true) {
            lock.lock();
            if (num > 0) {
                System.out.println(Thread.currentThread().getName() + "Sell one ticket and the rest" + num + "Zhang");
                num--;
            } else {
                lock.unlock();
                break;
            }
            lock.unlock();
            try {
                Thread.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        Thread thread1 = new Thread(ticket,"1");
        Thread thread2 = new Thread(ticket,"2");

        thread1.start();
        thread2.start();
    }
}

Operation results:

In the above code, we lock and unlock our code block through the lock method and unlock method of lock. Therefore, we can be sure that the conductor's ticket will not be disturbed by another conductor. The reason for adding sleep is that our results can better reflect the synchronization of threads.

3. Lock

In the above code, we have encountered synchronized and lock. Next, we will explain and learn the concept of lock, that is, its simple usage.

As the name suggests, locking is to lock something so that other visitors can't get the resource, so that the resource is exclusive during the locking period. Therefore, in the ticket sales process of the above conductor, there is no case of selling the same ticket, and there will be no oversold. The usage of synchronized and lock is very simple. You only need to add it on the outer layer of the code block you need to lock Just go up.

In this chapter, we will not delve into it, mainly to learn its basic usage, which will be explained in the following chapters.

Topics: Java Concurrent Programming