JUC Concurrent Programming: Shared Model Pipeline

0. Overall directory

  • Share questions
  • synchronized
  • Thread safety analysis
  • Monitor
  • wait/notify
  • Thread state transition
  • activity
  • Lock

Course address

1. Sharing issues

1.1 Problems caused by example sharing

  • Lao Wang (operating system) has a powerful abacus (CPU), and now he wants to rent it out to make some extra money

  • Xiaonan and Xiaonv (thread) use this abacus to perform some calculations and pay Laowang according to time.

  • But Xiaonan cannot use the abacus 24 hours a day. He often has to take a nap (sleep), or go to eat and use the toilet (blocking io operations). Sometimes he also needs a cigarette. When there is no cigarette, he has no ideas (wait). collectively called (blocking)

  • At these times, the abacus was not used (no money could be collected), and Lao Wang felt it was a bit uneconomical.

  • In addition, my daughter also wants to use the abacus. If Xiaonan always takes the abacus, it will make her feel unfair.

  • So, Lao Wang had an idea and thought of a way [Let each of them use the abacus for a while and take turns using the abacus]

  • In this way, when Xiaonan is blocked, the abacus can be distributed to Xiaonan for use without wasting it, and vice versa.

  • The calculations performed recently were relatively complex and needed to store some intermediate results, but the students' brain capacity (working memory) was not enough, so Lao Wang applied for a notebook (main memory) and recorded some intermediate results in the notebook first.

  • The calculation process is like this

  • But due to the time-sharing system, an accident happened one day

  • Xiaonan has just read the initial value 0 and performed a +1 operation, but has not had time to write back the result.

  • Lao Wang said [Xiaonan, your time is up, it's someone else's turn, remember the result and go], so Xiaonan muttered [The result is 1, the result is 1...] and reluctantly went to the side (context switch)

  • Lao Wang said [Little girl, it’s your turn]. The little girl saw 0 written on the notebook, did a -1 operation, and wrote the result -1 into the notebook.

  • At this time, the little girl's time also ran out, Lao Wang woke up Xiaonan again: [Xiaonan, please finish your last question], Xiaonan wrote the result 1 in his mind into the notebook

  • Xiaonan and Xiaonv both felt that they had done nothing wrong, but the result in the notebook was 1 instead of 0

1.2 Reflection of Java code

Two threads perform increment and decrement on a static variable with an initial value of 0, each 5000 times. Is the result 0?

static int counter = 0;
public static void main(String[] args) throws InterruptedException {
    
    
    Thread t1 = new Thread(() -> {
    
    
        for (int i = 0; i < 5000; i++) {
    
    
            counter++;
        }
    }, "t1");
    
    Thread t2 = new Thread(() -> {
    
    
        for (int i = 0; i < 5000; i++) {
    
    
            counter--;
        }
    }, "t2");
    
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    
    log.debug("{}",counter);
}

结果: -2048
每次都不一样

1.3 Problem analysis

        The above results may be positive numbers, negative numbers, or zero. why? Because the increment and decrement of static variables in Java are not atomic operations, to fully understand them, they must be analyzed from the bytecode

        For example, for i++(i is a static variable), the following JVM bytecode instructions will actually be generated:

getstatic i // 获取静态变量i的值
iconst_1 // 准备常量1
iadd // 自增
putstatic i // 将修改后的值存入静态变量i

        The memory model of Java is as follows. To complete the auto-increment and auto-decrement of static variables requires data exchange in main memory and working memory:

        If it is a single thread and more than 8 lines of code are executed sequentially (not interleaved), there is no problem:

        However, these 8 lines of code may run interleavedly under multi-threading:
        negative numbers may occur:

        In the case of positive numbers:

1.4 Critical Section

Critical Section: refers to a program fragment that accesses shared resources, and these shared resources cannot be accessed by multiple threads at the same time. When a thread enters a critical section, other threads or processes must wait. Some synchronization mechanisms must be implemented at the entry and exit points of the critical section to ensure that these shared resources are mutually exclusive.

  • There is no problem with a program running multiple threads
  • The problem lies in multiple threads accessing shared resources
    1. There is actually no problem with multiple threads reading shared resources.
    2. Problems will occur when instructions are interleaved when multiple threads read and write operations on shared resources.
  • If there are multi-threaded read and write operations on shared resources within a block of code, this block of code is called a critical section.

For example, the critical section in the following code

static int counter = 0;

static void increment()
// 临界区
{
    
    
    counter++; 
}

static void decrement()
// 临界区
{
    
    
    counter--; 
}

1.5 Race Condition

Multiple threads are executed in the critical section. Due to the different execution sequences of the code, the results are unpredictable, which is called a race condition.

2. synchronized

2.1 Mutual exclusion

   In order to avoid race conditions in critical sections, there are many ways to achieve the goal.

  • Blocking solution: synchronized, Lock.
  • Non-blocking solution: 原子变量.

        This class uses a blocking solution: synchronized to solve the above problem, commonly known as [Object Lock]. It uses a mutual exclusion method so that at most one thread can hold [Object Lock] at the same time, and other threads can think about it. It will be blocked when acquiring this [object lock]. This ensures that the thread that owns the lock can safely execute the code in the critical section without worrying about thread context switching.

Note:
    Although both mutual exclusion and synchronization in Java can be accomplished using the synchronized keyword, they are still different:

  • Mutual exclusion ensures that race conditions in the critical section occur, and only one thread can execute the critical section code at the same time.
  • Synchronization is due to the different execution sequence and order of threads, which requires one thread to wait for other threads to run to a certain point.

2.2 synchronized syntax

synchronized(对象) // 线程1, 线程2(blocked)
{
    
    
 	临界区
}

solve

static int counter = 0;
static final Object room = new Object();

public static void main(String[] args) throws InterruptedException {
    
    
    Thread t1 = new Thread(() -> {
    
    
        for (int i = 0; i < 5000; i++) {
    
    
            synchronized (room) {
    
    
                counter++;
            }
        }
    }, "t1");
    
    Thread t2 = new Thread(() -> {
    
    
        for (int i = 0; i < 5000; i++) {
    
    
            synchronized (room) {
    
    
                counter--;
            }
        }
    }, "t2");
    
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    log.debug("{}",counter);
}


You can make an analogy like this:

  • The object in synchronized (object) can be imagined as a room (room). There is a unique entrance (door). Only one person can enter the room at a time for calculation. Threads t1 and t2 can be imagined as two people.

  • When thread t1 executes to synchronized(room), it is like t1 enters the room, locks the door, takes away the key, and executes the count++ code in the door.

  • At this time, if t2 also runs to synchronized(room), it finds that the door is locked and can only wait outside the door. A context switch occurs and is blocked.

  • Even if t1's CPU time slice unfortunately runs out and is kicked out of the door (don't misunderstand that locking the object can continue to execute), the door is still locked at this time, t1 still holds the key, and t2 The thread is still blocked and cannot enter. You can only open the door and enter when it is t1's turn next time and you get the time slice again.

  • When t1 finishes executing the code in the synchronized{} block, it will come out of the obj room and unlock the door, wake up the t2 thread and give him the key. Only then can the t2 thread enter the obj room, lock the door, take the key, and execute its count-- code.

synchronizedIn fact, object locks are used to ensure that 原子性the code in the critical section is indivisible to the outside world and will not be interrupted by thread switching.
An atomic operation is an operation or a series of operations that cannot be interrupted.

Object-oriented improvements


class Room {
    
    
    int value = 0;
    public void increment() {
    
    
        synchronized (this) {
    
    
            value++;
        }
    }
    public void decrement() {
    
    
        synchronized (this) {
    
    
            value--;
        }
    }
    public int get() {
    
    
        synchronized (this) {
    
    
            return value;
        }
    }
}

@Slf4j
public class Test1 {
    
    

    public static void main(String[] args) throws InterruptedException {
    
    
        Room room = new Room();
        Thread t1 = new Thread(() -> {
    
    
            for (int j = 0; j < 5000; j++) {
    
    
                room.increment();
            }
        }, "t1");
        
        Thread t2 = new Thread(() -> {
    
    
            for (int j = 0; j < 5000; j++) {
    
    
                room.decrement();
            }
        }, "t2");
        
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        
        log.debug("count: {}" , room.get());
    }
}

2.3 Synchronized on methods

class Test{
    
    
    public synchronized void test() {
    
    

    }
}
等价于
class Test{
    
    
    public void test() {
    
    
        synchronized(this) {
    
    

        }
    }
}
class Test{
    
    
    public synchronized static void test() {
    
    
        
    }
}
等价于
class Test{
    
    
    public static void test() {
    
    
        synchronized(Test.class) {
    
    

        }
    }
}

The method of not adding synchronized
is like a person who does not follow the rules and does not queue up honestly (like someone who climbs in through a window)

3. The so-called "eight thread locks"

In fact, it is to examine synchronziedwhich object is locked.

Case 1: 12 or 21

@Slf4j(topic = "c.Number")
class Number{
    
    
    public synchronized void a() {
    
    
        log.debug("1");
    }
    public synchronized void b() {
    
    
        log.debug("2");
    }
}

public static void main(String[] args) {
    
    
    Number n1 = new Number();
    new Thread(()->{
    
     n1.a(); }).start();
    new Thread(()->{
    
     n1.b(); }).start();
}

Case 2: 12 after 1s, or 1 after 2 1s

@Slf4j(topic = "c.Number")
class Number{
    
    
    public synchronized void a() {
    
    
        sleep(1);
        log.debug("1");
    }
    public synchronized void b() {
    
    
        log.debug("2");
    }
}

public static void main(String[] args) {
    
    
    Number n1 = new Number();
    new Thread(()->{
    
     n1.a(); }).start();
    new Thread(()->{
    
     n1.b(); }).start();
}

Case 3: 3 1s 12 or 23 1s 1 or 32 1s 1

@Slf4j(topic = "c.Number")
class Number{
    
    
    public synchronized void a() {
    
    
        sleep(1);
        log.debug("1");
    }
    public synchronized void b() {
    
    
        log.debug("2");
    }
    public void c() {
    
    
        log.debug("3");
    }
}

public static void main(String[] args) {
    
    
    Number n1 = new Number();
    new Thread(()->{
    
     n1.a(); }).start();
    new Thread(()->{
    
     n1.b(); }).start();
    new Thread(()->{
    
     n1.c(); }).start();
}

Case 4: 2 1s later 1

@Slf4j(topic = "c.Number")
class Number{
    
    
    public synchronized void a() {
    
    
        sleep(1);
        log.debug("1");
    }
    public synchronized void b() {
    
    
        log.debug("2");
    }
}

public static void main(String[] args) {
    
    
    Number n1 = new Number();
    Number n2 = new Number();
    new Thread(()->{
    
     n1.a(); }).start();
    new Thread(()->{
    
     n2.b(); }).start();
}

Case 5: 2 1s later 1

@Slf4j(topic = "c.Number")
class Number{
    
    
    public static synchronized void a() {
    
    
        sleep(1);
        log.debug("1");
    }
    public synchronized void b() {
    
    
        log.debug("2");
    }
}

public static void main(String[] args) {
    
    
    Number n1 = new Number();
    new Thread(()->{
    
     n1.a(); }).start();
    new Thread(()->{
    
     n1.b(); }).start();
}

Case 6: 12 after 1s, or 1 after 2 1s

@Slf4j(topic = "c.Number")
class Number{
    
    
    public static synchronized void a() {
    
    
        sleep(1);
        log.debug("1");
    }
    public static synchronized void b() {
    
    
        log.debug("2");
    }
}

public static void main(String[] args) {
    
    
    Number n1 = new Number();
    new Thread(()->{
    
     n1.a(); }).start();
    new Thread(()->{
    
     n1.b(); }).start();
}

Case 7: 2 1s later 1

@Slf4j(topic = "c.Number")
class Number{
    
    
    public static synchronized void a() {
    
    
        sleep(1);
        log.debug("1");
    }
    public synchronized void b() {
    
    
        log.debug("2");
    }
}

public static void main(String[] args) {
    
    
    Number n1 = new Number();
    Number n2 = new Number();
    new Thread(()->{
    
     n1.a(); }).start();
    new Thread(()->{
    
     n2.b(); }).start();
}

Case 8: 12 after 1s, or 1 after 2 1s

@Slf4j(topic = "c.Number")
class Number{
    
    
    public static synchronized void a() {
    
    
        sleep(1);
        log.debug("1");
    }
    public static synchronized void b() {
    
    
        log.debug("2");
    }
}

public static void main(String[] args) {
    
    
    Number n1 = new Number();
    Number n2 = new Number();
    new Thread(()->{
    
     n1.a(); }).start();
    new Thread(()->{
    
     n2.b(); }).start();
}

4. Thread safety analysis of variables

appendix

1.Others ’ notes
2. JUC-Management

Guess you like

Origin blog.csdn.net/Blue_Pepsi_Cola/article/details/133234820