[Java Basics] A brief introduction to Java multi-threading life

Preface

1024 Programmer’s Day. Write something interesting today.

There are a lot of components and variables in Java's multi-threading. When you first learn, it's often hard to know what to do. Let's bring into life scenarios and briefly describe various usage scenarios in Java.


The basic composition of multi-threading Thread (multiple people working at the same time)

In daily life, we often need to do one thing. We think of it as a Task. Then a Task can sometimes be divided into multiple identical Tasks and multiple different Tasks.

For example: A dumpling shop sells dumplings

  • Scenario 1-1: Quick-frozen dumplings are used. The store owner only needs to be responsible for placing the dumplings. So when there are many customers, one pot is naturally not enough, and we need to prepare 5 pots for placing the dumplings. Then, you
    can It is thought that there are 5 workers performing the dumpling operation at the same time. Picture:

At this point, we can write pseudocode like this.

// 下饺子
Task cookDumpling.
// 准备5个工人进行下饺子
Thread worker1 = new Thread(cookDumpling);
Thread worker2 = new Thread(cookDumpling);
Thread worker3 = new Thread(cookDumpling);
Thread worker4 = new Thread(cookDumpling);
Thread worker5 = new Thread(cookDumpling);
// 发号司令 5个人开始干活
worker1.start();
worker2.start();
worker3.start();
worker4.start();
worker5.start();
  • Scenario 1-2: Of course, we don’t want these five workers to stop working once they finish making dumplings. (They are old capitalists.) Therefore, we hope they will work in cycles. And they should not stop when there is no work. Work, but wait. Therefore, we can separate the worker's task and run methods. It can be written as pseudo code like this.
// 工作池. 所有的需求不断向工作池中放即可.
List<Task> queueTaskList = new Queue();

// 任务类
class Task{
    // 任务的Id
    int id;
    // 订单是啥 - 比如这个客人订了2份鲜肉水饺. 2份韭菜水饺.
    Order order;
}
// 工作类
class Worker{
    public void work(){
        // 查看当前时间是否是工作时间. 如果是工作时间. 就给我不停的干活.    
        while(currentTime>startTime & currentTime<endTime){
            if(queueTaskList.isNotEmpty()){
                // 弹出最前的需要执行的任务                
                var task = queueTaskList.pop();
                // 执行这个任务
                task.perform();
            }else{
                // 否则过1分钟过后. 再来查看工作池中是否有工作需要干.            
            }
        }
    }
}
  • Scenario 1-3: Sometimes, when we provide services, we can choose specialized personnel. For example, when seeing a doctor, we hope to choose specialized doctors. When repairing water pipes, we want to choose specialized plumbers. (Or call technicians, hehe.)

This kind of use is not difficult. We can just rewrite the task distribution and extraction methods.

class Task{
    // 需要指定的workerid 指定你需要执行任务的技师
    String workerId;
}
// 工作类
class Worker{
    public void work(){
        while(currentTime>startTime & currentTime<endTime){
            if(queueTaskList.isNotEmpty()){
                // 写的简单点. 我们可以判断当前头部任务是否是需要我来做的.
                var task = queueTaskList.head();
                if(task.workerId.equals(this.id)){
                    // 指定的技师是当前工人. 那么就由我来拯救你吧.
                    // 从任务池中接下任务, 然后执行任务.
                    var task = queueTaskList.pop();
                    task.perform();
                }else{
                    // 休息10秒中. 等其他技师接下任务.
                }

            }else{
                // 否则过1分钟过后. 再来查看工作池中是否有工作需要干.            
            }
        }
    }
}

Multiple people working on different jobs - no interrelationships

The above description mainly explains how we can let multiple people collaborate to complete the same thing. Of course, in life, this is not the case we often encounter. What we often encounter is that we need to execute two different things at the same time. The same thing. Let’s take making dumplings as an example. We all need to buy vegetables. 1. We need to buy 10 pounds of meat. 2. We need to buy 10 pounds of leeks. So these two things can be done by two people at the same time. Of course. , you can let 2 people buy these 10 pounds of meat. It all depends on your preference. Our pseudocode should be written like this:

// 买韭菜
Task buyChives
// 买肉
Task buyMeat.
// 准备2个工人分别干这个活
Thread worker1 = new Thread(buyMeat);
Thread worker2 = new Thread(buyChives);
// 发号司令 2个人开始干活
worker1.start();
worker2.start();

Of course, you can ask two people to buy these 10 pounds of meat. It all depends on your preference. At this time, the pseudo code should be written like this.

// 买韭菜
Task buyChives
// 买肉
Task buyMeat.
// 准备3个工人分别干这个活
Thread worker1_1 = new Thread(buyMeat);
Thread worker1_2 = new Thread(buyMeat);
Thread worker2 = new Thread(buyChives);
// 发号司令 3个人开始干活
worker1_1.start();
worker1_2.start();
worker2.start();

Multiple people working on different jobs - interrelated

In life, the things we often encounter are sometimes not just shopping for groceries. They are two or more things that are more complicated and interdependent. So what should we do?

Let's take making dumplings as an example. We need people to 1. buy vegetables, 2. make dumplings and 3. place dumplings. We choose 3 waves of people to start doing it at the same time. But... these 3 things are dependent. In order to pursue convenience and speed. We agreed that 2 people buy vegetables. 2 people make dumplings. 2 people make dumplings. And the person making dumplings needs to wait until the grocery shopping is finished before they can execute it. The person making dumplings must wait until the making of dumplings is finished before they can execute it. Then, this assembly line
operation , is formed. We can set the following switches.

// 买菜任务
Task bugVegetable
// 包饺子任务
Task makeDumplings
// 下饺子任务
Task cookDumplings

// 准备6个工人分别干这个活
Thread worker1_1 = new Thread(bugVegetable);
Thread worker1_2 = new Thread(bugVegetable);
Thread worker2_1 = new Thread(makeDumplings);
Thread worker2_2 = new Thread(makeDumplings);
Thread worker3_1 = new Thread(cookDumplings);
Thread worker3_2 = new Thread(cookDumplings);

// 买菜先行
worker1_1.start();
worker1_2.start();

// 等待买菜结束
worker1_1.join();
worker1_2.join();

// 包饺子第二
worker2_1.start();
worker2_2.start();

// 等待包饺子结束
worker2_1.join();
worker2_2.join();

// 下饺子第三
worker3_1.start();
worker3_2.start();

The waiting here is of course in Java, and can be implemented using Thread's Join method or tool classes such as CountDownLatch. This is omitted here.


Lock

There are actually many types of lock implementations in Java. But in daily life, the abstraction of our locks is the same. So we will not give real examples for mapping here.

  • Locks in Java: Synchronized
  • Java锁: ReentanckLock / ReentrackReadWriteLock
  • Lock tool class: CountDownLatch / CycleBarrier / Semaphore
  • Current limiter: Semaphore
  • Exchanger: Exchanger
  • Visual synchronization keyword: volatile

The implementation of locks can also be implemented in Java through direct compilation parameters or CAS.

Due to the limited space here, I will not go into details here.


communication

Communication in Java can actually be divided into signal communication and direct communication between threads. Signal communication is mainly the wait and notify methods of Thread. Of course, there is also the condition method in ReentackLock. For direct communication, you can write through methods and message pools 2 kinds.

  • Signal communication: wait/notify/notifyAll | condition
  • Message communication: sendMessage(People people, Message message) | Message pool (message channel)

Asynchronous operations

In our daily life, things are often not synchronous operations. What is a synchronous operation and what is an asynchronous operation? It is very simple. For example:

  • The boss asked Xiao Wang to buy groceries. Before Xiao Wang came back from shopping, the boss kept waiting. This is a synchronous operation. (I’m afraid it’s not the boss but the landlady, and it’s the boss who buys the groceries. HAHA)

  • The boss asks Xiao Wang to buy groceries. Before Xiao Wang comes back from shopping, the boss will do some other things, such as playing games and waiting. This is an asynchronous operation.

### 同步伪代码
// 买菜任务
Task bugVegetable
Thread workerWang = new Thread(bugVegetable);
workerWang.start();
// 老板同步等待
workerWang.join();
### 异步伪代码
// 买菜任务
Task bugVegetable
Thread workerWang = new Thread(bugVegetable);
workerWang.start();
// 老板干其他事情 - 比如打游戏
game();

// 老板等待 - 老板等不及了. 要记账. 咋还没回来呢. 开始同步等待. 获取买菜结果.
workerWang.join();
### 异步伪代码 Future
// 买菜任务
Task bugVegetable
Future<Callable> workerWang = new Thread(bugVegetable);
workerWang.start();
// 老板干其他事情 - 比如打游戏
game();

// 老板等待 - 老板等不及了. 要记账. 咋还没回来呢. 开始同步等待. 获取买菜结果.
workerWang.get();

There is actually no difference between the two pseudocodes here. The main one is using the join method of Java's Thread, and the other is using the get method of Future. In fact, there is no difference in essence.


Atomic operations

The smallest node in daily life. For example, when you do homework, it is not the smallest node. Doing math homework is not the smallest node. Neither is calculating a math problem. But the calculation work of "1+1=2" can be considered as an atom in life. operational.


Secure sync container

For example, we sometimes have two people buying groceries at the same time, and they both withdraw money from the accounting room.
At this time, the amount in the accounting room is 1000, and A takes 100, and will write 900.
At this time, B also withdraws 100, and will also write 900. Write. At this time, B writes 900.
At this time, A also writes 900.
Then the account amount at this time is 900. Then both people took 100. So who lost... HEI~ HEI~ Where did the 100 go?

Answer: In fact, the accounting office obviously suffered a loss. 200 was withdrawn. It was written as 100. The accounting office actually had 800 left, but it was written as 900. There was a difference of 100.

The overall process is as follows:

  • Accounting room 1000.
  • A takes 100. Calculate that there are 900 left.
  • B takes 100. Calculate that there are 900 left.
  • B writes 900.
  • A writes 900
  • There are 800 left in the accounting room. But the account is recorded as 900. 100 is wrong.
So what to do?
  • Each time it is taken, it is based on -100 this time. But this method will probably reduce it to a complex number. (uptdate total=total-100 where total >=100;)
  • Forced lock. You can use volatile or sychronized ideas.
### synchronized的思路 (整个更新全部加锁. 悲观锁.)
> * 账房1000.
> * 锁定账房. A取100. 计算出还剩900.
> * B取100. 失败. 因为已经被A锁定了. 无法获取.
> * A写入900. 释放锁.
> * B获取锁. 锁定账房. B取100, 计算出还剩800.
> * B写入800. 释放锁.
> * 账房结束还剩800. 记账800. 此时就是正确的.
### volatile思路 (只在写入时加乐观锁)
> * 账房1000.
> * 锁定账房. A取100. 计算出还剩900.
> * B取100. 计算出还剩900.
> * A写入900. 释放锁.
> * B获取锁. 发现账房已经被A动过. 重新计算. 计算出等于800.
> * B写入800. 释放锁.
> * 账房结束还剩800. 记账800. 此时就是正确的.

Work Pool & Work Pool Framework

In life, there is often a set of plans. For example, assign 2 people to buy vegetables and 2 people to make dumplings. Then 2 people will do sales and 2 people will do accounting. We often think of such a plan as a thread pool. In Java, we are
very There are many types of thread pools. For example, thread pools with fixed number of people, thread pools with variable number of people, and scheduled thread pools. Of course, in order to make solutions like thread pools easier to use, we have also packaged a layer of Executors. This is not this article. The main topic. So I will simply skip it briefly.


Summarize

The main body of this article introduces multi-threading in Java and real-life usage scenarios. Obviously, you can see the Java-related shadow in it.

  • Worker: Thread
  • Thread synchronization: Join
  • Thread asynchronous: Future Callable
  • Other synchronization tools: CountDownLatch / CycleBarrier / Semaphere
  • Lock: Synchronized / ReentackLock / non-lock tool volatile
  • Atomic operations: AtomicInteger
  • Message communication: wait & notify / condition / message Queue
  • Thread pool: ThreadPool / Executor

Guess you like

Origin blog.csdn.net/u010416101/article/details/120943282