The story of concurrent programming - the shared model of concurrency

Shared model of concurrency


1. Sharing issues caused by multi-threading

For example, when a shared resource is used by two threads. Thread 1 performs a +1 operation on variable i=0, but switches to thread 2 on the way to +1. Thread 2 performs a -1 operation on variable i, and then returns -1 to the shared variable. But switching back to thread 1 is already +1, that is, the local variable is already 1, and reassigning to the shared variable will cause concurrency problems. The second is that
if the shared resource is always used by a thread, the thread may be wasted due to sleep, wait, io and other operations. CPU usage time, then you can give this time to others

Problem analysis
The bytecode of i++ is
getstatic i to obtain the static variable i, which is the i of the main memory.

iconst_1 prepare constant 1

iadd adds

pustatic i

So the i++ we see is not an atomic instruction. Since it is not an atomic instruction, the thread can naturally cause these instructions to be interleaved during the switching process. In the end, shared resources are a problem of dirty data.

The critical section
actually means that there are shared resources in the code accessed by multiple threads, so this code is the critical section.

Race condition:
If multiple threads in a critical section have different execution instruction sequences and the results are unpredictable, it is a race condition.

2. Solution

① After synchronize
thread 1 is locked, thread 2 cannot obtain the lock and cannot execute the critical section. Thread 2 blocks and waits for thread 1 to complete releasing the lock before it can be used. Synchronize can be compared to a room. Every time a person with a lock can enter the room to do things, even if the CPU time slice is used up, other threads cannot enter the room without the key of the lock. When finished, the lock will be released and the blocked threads will be awakened.
Insert image description here

static int count=0;
    static Object lock=new Object();
    public static void main(String[] args) throws InterruptedException {
    
    
        Room room = new Room();
        Thread t1 = new Thread(() -> {
    
    
            for (int i = 0; i < 5000; i++) {
    
    
//                room.increment();
                synchronized (lock){
    
    
                    count++;
                }

            }
        }, "t1");

        Thread t2 = new Thread(() -> {
    
    
            for (int i = 0; i < 5000; i++) {
    
    
//                room.decrement();
                synchronized (lock){
    
    
                    count--;
                }

            }
        }, "t2");

        t1.start();
        t2.start();
        t1.join();
        t2.join();
//        log.debug("{}", room.getCounter());
        log.debug("{}",count);
    }

Thinking
① If synchronize is placed outside for? In fact, the lock will be released only after the entire for is executed - atomicity
② What happens if t1 and t2 use different obj? It is equivalent to entering a different room, so the lock has no effect, and the two threads will still execute the code blocks in the critical section separately, resulting in a different execution sequence.
③If t1 is locked, but t2 is not? That is equivalent to t2 can execute the critical section at any time

The object-oriented improvement
is actually to encapsulate all critical sections and shared resources that need to be locked into one class. Adding synchronize to the method is equivalent to locking this. If it is a static method, it is equivalent to locking the class object.class

class Room {
    
    
    private int counter = 0;

    public synchronized void increment() {
    
    
        counter++;
    }

    public synchronized void decrement() {
    
    
        counter--;
    }

    public synchronized int getCounter() {
    
    
        return counter;
    }
}

3. synchronize in the method

Eight thread locks
Case 1: When there is no sleep, n1 locks its own object when executing methods a and b. So are mutually exclusive.
Situation 2: With sleep, it is still the same. The two threads executing methods a and b will see who gets the lock first. Then whoever executes first. Regardless of whether there is sleep in it, threads that have not acquired the lock have to wait.

@Slf4j(topic = "c.Test8Locks")
public class Test8Locks {
    
    
    public static void main(String[] args) {
    
    
        Number n1 = new Number();
//        Number n2 = new Number();
        new Thread(() -> {
    
    
            log.debug("begin");
            n1.a();
        }).start();
        new Thread(() -> {
    
    
            log.debug("begin");
            n1.b();
        }).start();
    }
}
@Slf4j(topic = "c.Number")
class Number{
    
    
    public synchronized void a() {
    
    
        sleep(1);
        log.debug("1");
    }
    public synchronized void b() {
    
    
        log.debug("2");
    }
}

Case 3: In this case c is not locked, that is, it can be executed at will. The possible results are 3 12, 32 1, or 23 1.

@Slf4j(topic = "c.Test8Locks")
public class Test8Locks {
    
    
    public static void main(String[] args) {
    
    
        Number n1 = new Number();
//        Number n2 = new Number();
        new Thread(() -> {
    
    
            log.debug("begin");
            n1.a();
        }).start();
        new Thread(() -> {
    
    
            log.debug("begin");
            n1.b();
        }).start();

        new Thread(() -> {
    
    
            log.debug("begin");
            n1.c();
        }).start();

    }
}
@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");
    }
}

Case 4: The locks they bind are all different, which is equivalent to no lock. The final result must be 21. Because 1 thread sleeps

@Slf4j(topic = "c.Test8Locks")
public class Test8Locks {
    
    
    public static void main(String[] args) {
    
    
        Number n1 = new Number();
        Number n2 = new Number();
        new Thread(() -> {
    
    
            log.debug("begin");
            n1.a();
        }).start();
        new Thread(() -> {
    
    
            log.debug("begin");
            n2.b();
        }).start();

//        new Thread(() -> {
    
    
//            log.debug("begin");
//            n1.c();
//        }).start();

    }
}
@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");
//    }
}

In cases 5-8, static is added, so just judge whether they have the same lock based on the objects they lock.

4. Thread safety analysis of variables

Are there thread safety issues with static variables and member variables?
If it is just reading, then there is none. If it is reading and writing, you need to pay attention to the critical section.

Local variables?
If it is a reference type, then yes.
The value of local variables is stored in the thread's stack frame, which is private. Instead of taking out the variable from the method area like a static variable and then modifying it accordingly.
Insert image description here
Situation 1: The ThreadUnsafe list is created in the class, which will cause thread safety issues caused by the thread processing the same list in the heap.

Case 2: Put the list in method1, then it is a reference to a local variable, and each thread has its own list after calling the method. Then there will be no thread safety issues

Case 3: If a subclass rewrites this method, the list can also be obtained, resulting in multiple threads being able to operate the list. The solution is to add final to the method to prevent subclass rewriting.

public class TestThreadSafe {
    
    

    static final int THREAD_NUMBER = 2;
    static final int LOOP_NUMBER = 200;
    public static void main(String[] args) {
    
    
//        ThreadSafeSubClass test = new ThreadSafeSubClass();
        ThreadUnsafe test=new ThreadUnsafe();
        for (int i = 0; i < THREAD_NUMBER; i++) {
    
    
            new Thread(() -> {
    
    
                test.method1(LOOP_NUMBER);
            }, "Thread" + (i+1)).start();
        }
    }
}
class ThreadUnsafe {
    
    
    ArrayList<String> list = new ArrayList<>();
    public void method1(int loopNumber) {
    
    
        for (int i = 0; i < loopNumber; i++) {
    
    
            method2();
            method3();
        }
    }

    private void method2() {
    
    
        list.add("1");
    }

    private void method3() {
    
    
        list.remove(0);
    }
}

class ThreadSafe {
    
    
    public final void method1(int loopNumber) {
    
    
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < loopNumber; i++) {
    
    
            method2(list);
            method3(list);
        }
    }

    public void method2(ArrayList<String> list) {
    
    
        list.add("1");
    }

    private void method3(ArrayList<String> list) {
    
    
        System.out.println(1);
        list.remove(0);
    }
}

class ThreadSafeSubClass extends ThreadSafe{
    
    
//    @Override
    public void method3(ArrayList<String> list) {
    
    
        System.out.println(2);
        new Thread(() -> {
    
    
            list.remove(0);
        }).start();
    }
}

Insert image description here
Thread-safe class
Integer
HashTable
String
Random
Vector
Class under JUC

Their individual methods are thread-safe, but it is different when multiple methods are executed.
The problem with the following code is that thread 1 switches after successful judgment and just releases the lock. Then thread 2 acquires the lock for judgment, switches thread 1 again to acquire the lock and processes the put, and switches thread 2 to acquire the lock and process the put. Because a single method will release the lock after execution. Therefore, it still needs to be locked as a whole before processing can continue.
Insert image description here
Immutable thread-safe
String
String and Integer are both immutable. String is essentially a char[] array. If it is a substring method, it actually copies a new array and then assigns it to the char array of String. Replace actually creates an array, and then compares it with the old value of the array. If it is an old value, it directly assigns a new value to that position in the new array.

public String replace(char oldChar, char newChar) {
    
    
        if (oldChar != newChar) {
    
    
            int len = value.length;
            int i = -1;
            char[] val = value; /* avoid getfield opcode */

            while (++i < len) {
    
    
                if (val[i] == oldChar) {
    
    
                    break;
                }
            }
            if (i < len) {
    
    
                //创建新数组
                char buf[] = new char[len];
                for (int j = 0; j < i; j++) {
    
    
                    buf[j] = val[j];
                }
                while (i < len) {
    
    
                    char c = val[i];
                    //根据原来的数组是旧值的位置改变成新值
                    buf[i] = (c == oldChar) ? newChar : c;
                    i++;
                }
                return new String(buf, true);
            }
        }
        return this;
    }
 public String substring(int beginIndex) {
    
    
        if (beginIndex < 0) {
    
    
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {
    
    
            throw new StringIndexOutOfBoundsException(subLen);
        }
     //实际上就是创建了一个新的String,而不是修改了值
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}


  public String(char value[], int offset, int count) {
    
    
        if (offset < 0) {
    
    
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count <= 0) {
    
    
            if (count < 0) {
    
    
                throw new StringIndexOutOfBoundsException(count);
            }
            if (offset <= value.length) {
    
    
                this.value = "".value;
                return;
            }
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > value.length - count) {
    
    
            throw new StringIndexOutOfBoundsException(offset + count);
        }
      //实际上就是创建新数组并且进行复制
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }

Example analysis:
This kind of thread is not safe because the MyServlet is shared and the UserService is in the heap. Its methods can be called by multiple threads to modify count, which may cause concurrency problems.
Insert image description here
This also has concurrency issues. Singleton sharing can be called by multiple multi-threads, covering variables like start.
Insert image description here
This does not cause thread safety issues because no variables can be modified.
Insert image description here
Thread safety issues occur here because the Connection is exposed and can be processed by multiple threads. If thread 1 switches to thread 2 when processing getCon and it happens to close, then thread 1's Connection cannot continue to execute because it has been modified.
Insert image description here
There is no thread safety issue here because a UserDao created each time is equivalent to a local variable. It is not a shared resource.
Insert image description here
Thread safety issues may arise here because local variables are exposed through subclass methods and may be modified by subclass objects through threads. Summary: Whether
Insert image description here
thread safety depends on whether it can be modified. shared resources

5. Exercises

The thread safety problem occurs when multiple threads may be selling tickets and the shared variable count is taken out at the same time. The final count number is the value processed by the last thread. Instead of jointly processed values, because an error occurred in their execution sequence, the count was read in by another thread without waiting for processing.

The solution is to lock the critical section, which is actually the sell of the window. So why not lock amountList and window? The reason is that they are not operating the same shared resource and handle it differently, so there is no need to lock. However, the previous two operations of HashTable, get and put, were targeted at the same shared resource, causing the last value to be overwritten by the last thread. Because thread 1 has not completed put when the empty judgment is successful. Moreover, the two operations are not locked, but one is done and the other is

@Slf4j(topic = "c.ExerciseSell")
public class ExerciseSell {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        // 模拟多人买票
        TicketWindow window = new TicketWindow(1000);

        // 所有线程的集合
        List<Thread> threadList = new ArrayList<>();
        // 卖出的票数统计
        List<Integer> amountList = new Vector<>();
        for (int i = 0; i < 2000; i++) {
    
    
            Thread thread = new Thread(() -> {
    
    
                // 买票

                try {
    
    
                    Thread.sleep(random(10));
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                int amount = window.sell(random(5));
                // 统计买票数
                amountList.add(amount);
            });
            threadList.add(thread);
            thread.start();
        }

        for (Thread thread : threadList) {
    
    
            thread.join();
        }

        // 统计卖出的票数和剩余票数
        log.debug("余票:{}",window.getCount());
        log.debug("卖出的票数:{}", amountList.stream().mapToInt(i-> i).sum());
    }

    // Random 为线程安全
    static Random random = new Random();

    // 随机 1~5
    public static int random(int amount) {
    
    
        return random.nextInt(amount) + 1;
    }
}

// 售票窗口
class TicketWindow {
    
    
    private int count;

    public TicketWindow(int count) {
    
    
        this.count = count;
    }

    // 获取余票数量
    public int getCount() {
    
    
        return count;
    }

    // 售票 synchronized
    public  int sell(int amount) {
    
    
        if (this.count >= amount) {
    
    
            this.count -= amount;
            return amount;
        } else {
    
    
            return 0;
        }
    }
}

Vector自己的方法就已经带锁

public synchronized boolean add(E e) {
    
    
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

Transfer problem
Thread safety issues actually occur here. The main thing is that when a transfers money, b also transfers money. Then what b gets is definitely a without transfer, and the same is true for a. So is it okay to add synchronize directly to the method? If you bind an object of this type, it obviously won't work, because there are two accounts with different locks and they enter different rooms. Then the solution is to use the only Account.class class, then it can be locked

@Slf4j(topic = "c.ExerciseTransfer")
public class ExerciseTransfer {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Account a = new Account(1000);
        Account b = new Account(1000);
        Thread t1 = new Thread(() -> {
    
    
            for (int i = 0; i < 1000; i++) {
    
    
                a.transfer(b, randomAmount());
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
    
    
            for (int i = 0; i < 1000; i++) {
    
    
                b.transfer(a, randomAmount());
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        // 查看转账2000次后的总金额
        log.debug("total:{}", (a.getMoney() + b.getMoney()));
    }

    // Random 为线程安全
    static Random random = new Random();

    // 随机 1~100
    public static int randomAmount() {
    
    
        return random.nextInt(100) + 1;
    }
}

// 账户
class Account {
    
    
    private int money;

    public Account(int money) {
    
    
        this.money = money;
    }

    public int getMoney() {
    
    
        return money;
    }

    public void setMoney(int money) {
    
    
        this.money = money;
    }

    // 转账
    public void transfer(Account target, int amount) {
    
    
        synchronized(Account.class) {
    
    
            if (this.money >= amount) {
    
    
                this.setMoney(this.getMoney() - amount);
                target.setMoney(target.getMoney() + amount);
            }
        }
    }
}

6. Monitor

The java object header
includes markword, which mainly stores hashcode, age (gc life value), whether bias_lock is biased lock, 01 lock situation
, and klassword, which mainly points to class objects (class information).
If it is an array, it also includes the length of the array
Insert image description here
. Monitor lock The
monitor lock is provided by the os and is very costly.
The working principle
is actually to record the address of the monitor in the first 30 bits of the markword of obj and point to the monitor. Then if a thread wants to execute the critical section code, point the owner of the monitor to the corresponding thread. If another thread comes in, then see if obj is associated with a lock, and then see if there is an owner. If so, then enter the EntryList to block and wait. After waiting for the thread to release the lock, wake up the EntryList and start the competition again.
Insert image description here
From the bytecode perspective,
the bytecode perspective is to first copy the reference of the lock to slot1, then monitorentry, point the markword of the lock to the monitor, and store the hashcode etc. in the monitor. Then the business code is executed, and finally the reference slot1 is taken out, and then monitorexit is unlocked. Moreover, the business code, that is, the synchronization block, is monitored for exceptions. If an exception occurs, the unlocking operation will still be performed.
Insert image description here

7. Synchronize optimization

Small story
If there is no competition between the two threads, then you can use a lightweight lock, which is equivalent to hanging a schoolbag. If you find that it is the other party's schoolbag, then wait outside. Later, when another thread is not used, another thread can engrave its name on the door, which is equivalent to a biased lock. If someone comes to compete at this time, it will be upgraded to a schoolbag, which is a lightweight lock. Later, another thread came back and found that that thread had engraved names on many doors, so he went to the os to engrave those names into his own in batches. That is to modify the bias lock. In the end, too many names were engraved to cancel the bias.

Biased locks are exclusive to a single thread. If a single thread processes a certain code without competition, then biased locks can be used. If there is competition, then it can be upgraded to a lightweight lock.
1. Lightweight lock
The essence is that the lock record of the stack frame of the thread calling the temporary area method saves the object reference and the object markword information. The next step is to exchange the lock information of the corresponding lock record with obj. For example, changing 01 to 00 tells obj that this is a lightweight lock, and tells obj the address of the lock record, which is equivalent to labeling obj who it is. lock label. If it is a reentrant lock, then the markword part of the lock record is null, indicating that it is reentrant and uses the same lock.
Insert image description here
2. Lock expansion
actually means that when competing for lightweight locks, there is no place for competing threads. Then at this time, it is necessary to convert the lightweight lock into a heavyweight lock monitor. In fact, it is to point the markword of obj to the monitor. Then the owner of the monitor points to the lock record of the current thread. Put the blocked thread into the waiting queue.
When recovering, CAS tried to restore the thread's lock record to the past, but found that it failed. At this time, the recovery method is changed to the heavyweight lock recovery method, the list is woken up, and then the owner is set to null, and the thread competes for the monitor again. If not, restore the hashcode information saved by the monitor.
Insert image description here
3. Spin optimization
is actually equivalent to waiting for a traffic light. If you get to the green light soon, wait for a while, and if it takes a long time, then pull the handbrake. Spinning is to spin for a while and wait for others to release the heavyweight lock. If it succeeds once, then the next time it will be sure that the probability of success will increase, and the number of spins will be increased. If it doesn't wait, block.
The reason for the spin: blocking will cause the context switch of the thread to consume cpu time and resources. The speed is relatively slow.

4. Bias lock
The reason why bias lock is used is because the lightweight lock calls CAS for comparison every time the lock is reentrant. CAS is an OS instruction operation so it is very slow. Therefore, the biased lock is to directly assign the ThreadId to the markword, so that the markword can be directly compared in Java next time.
Biased locks are delayed and usually will not be generated until a while after the object is created. Generate
biased locks first - "lightweight locks -" heavyweight locks
. If biased locks are used for critical sections, then the id of the corresponding execution thread is assigned to markword.
If used If the hashcode of the lock is missing, then the biased lock will be disabled because the hashcode occupies too many bits. The
lightweight one records the hashcode on the lock record, and the heavyweight one records it on the monitor.
If two threads use the same bias-level lock, then the lock will It becomes non-biasable and upgraded to a lightweight lock.
Insert image description here
Batch re-biasing
is actually multiple threads without competition, using the same lock. If the jvm finds that the number of canceled lock biases exceeds 20 times, it will automatically bias to another thread. For example, the t1 thread uses a bunch of locks, and the locks are biased towards t1. But if t2 uses these locks and needs to revoke the lock but biases it more than 20 times, then these locks will all bias to t2

Batch undo:
If the undo exceeds 40 times, the jvm will undo the bias of all objects.

5. Lock elimination.
In fact, JIT finds that there are no shared resources in the critical section of the lock, so it cancels the lock.
Insert image description here

8. wait and notify

Short story:
Xiaonan needs cigarettes to work, but he also has to occupy the lock to prevent others from entering. Then opening a waitSet at this time is equivalent to letting Xiaonan go to the lounge to relax and release the lock. If the smoke arrives, notify Xiaonan can continue to work.

The difference between Blocked and Waiting
is actually that waiting releases the lock, while blocked means that there is no lock.
After waiting is notified, it still needs to enter the entrylist to wait for
Insert image description here
the rules of wait and notify.
When the thread calls the object wait and notify, it must use this lock to become the owner of the monitor. when. In this way, you can wait to release the lock and be notified by other people who acquire the lock.

@Slf4j(topic = "c.TestWaitNotify")
public class TestWaitNotify {
    
    
    final static Object obj = new Object();

    public static void main(String[] args) {
    
    

        new Thread(() -> {
    
    
            synchronized (obj) {
    
    
                log.debug("执行....");
                try {
    
    
                    obj.wait(); // 让线程在obj上一直等待下去
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                log.debug("其它代码....");
            }
        },"t1").start();

        new Thread(() -> {
    
    
            synchronized (obj) {
    
    
                log.debug("执行....");
                try {
    
    
                    obj.wait(); // 让线程在obj上一直等待下去
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                log.debug("其它代码....");
            }
        },"t2").start();

        // 主线程两秒后执行
        sleep(0.5);
        log.debug("唤醒 obj 上其它线程");
        synchronized (obj) {
    
    
//            obj.notify(); // 唤醒obj上一个线程
            obj.notifyAll(); // 唤醒obj上所有等待线程
        }
    }
}

The wait() method can limit the waiting time, wait(parameter)

9. sleep and wait

The difference is
sleep: Thread call, static method, and will not release the lock.
wait: all obj, but it must be used with synchronized to release the lock.
Extension
. Usually the lock will be added with final to prevent it from being modified.

Correct use
Xiaonan needs smoke to work. If you use sleep without releasing the lock, then other people who need to wait for work will wait. But wait can let Xiaonan release the lock, let other threads work, and wake up Xiaonan.
There is a problem
. Are there other threads waiting for the lock? If so, will the wrong thread be awakened?

@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep2 {
    
    
    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;

    public static void main(String[] args) {
    
    
        new Thread(() -> {
    
    
            synchronized (room) {
    
    
                log.debug("有烟没?[{}]", hasCigarette);
                if (!hasCigarette) {
    
    
                    log.debug("没烟,先歇会!");
                    try {
    
    
                        room.wait(2000);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
                log.debug("有烟没?[{}]", hasCigarette);
                if (hasCigarette) {
    
    
                    log.debug("可以开始干活了");
                }
            }
        }, "小南").start();

        for (int i = 0; i < 5; i++) {
    
    
            new Thread(() -> {
    
    
                synchronized (room) {
    
    
                    log.debug("可以开始干活了");
                }
            }, "其它人").start();
        }

        sleep(1);
        new Thread(() -> {
    
    
            synchronized (room) {
    
    
                hasCigarette = true;
                log.debug("烟到了噢!");
                room.notify();
            }
        }, "送烟的").start();
    }

}

Solution:
You can use while to determine whether the condition is true multiple times, and directly use notifyAll to wake up all threads. Then after the thread is awakened, it can first judge whether the condition is true. If it is true, then execute the following. If it is not true, then continue to wait.
Insert image description here

10. park and unpark

The difference between wait and notify
is that it does not need to be used with a monitor.
It can accurately wake up and block threads
. You can unpark first, but you cannot notify first. But after unpark, park doesn’t work.

Working principle
①park, first go to the counter to determine whether it is 0, if so, let the thread enter the queue, and set the counter to 0 again
②unpark, if the thread is blocking, first set the counter to 1, then wake up the thread and resume Run and set counter to 0
③ Unpark first and then park, then unpark and add counter to 1. Park determines that counter is 1. If you think you still have energy, continue execution.

Eleven, re-understand the thread state

Case 1: new->runnable
thread start
Case 2: runnable->waiting
notify and wait. wait enters blocking, notify allows them to compete for the lock again and enter runnable. Others still enter blocked
situation 3.
park and unpark
situation 4.
The thread calling it by t.join will enter waiting, waiting for t to complete the task or be interrupted.
Situations 5-8
are actually wait. , join, sleep plus time, all from runnable->blocked
situation 9
If synchronize fails to acquire the lock, it will enter blocked
situation 10
After all the code is executed, it will be terminated
Insert image description here

12. Multiple locks

One room for sleeping and studying. But when only one lock is sleeping, it cannot learn and the concurrency is very low. Then at this time, you can refine the granularity of the lock and divide it into two locks, one for the study room and one for the bedroom, so that the two functions can be executed concurrently.

Problem:
If there are too many locks and one thread needs multiple locks, deadlock will occur.

public class TestMultiLock {
    
    
    public static void main(String[] args) {
    
    
        BigRoom bigRoom = new BigRoom();
        new Thread(() -> {
    
    
            bigRoom.study();
        },"小南").start();
        new Thread(() -> {
    
    
            bigRoom.sleep();
        },"小女").start();
    }
}

@Slf4j(topic = "c.BigRoom")
class BigRoom {
    
    

    private final Object studyRoom = new Object();

    private final Object bedRoom = new Object();

    public void sleep() {
    
    
        synchronized (this) {
    
    
            log.debug("sleeping 2 小时");
            Sleeper.sleep(2);
        }
    }

    public void study() {
    
    
        synchronized (this) {
    
    
            log.debug("study 1 小时");
            Sleeper.sleep(1);
        }
    }

}

Deadlock case
t1 has A but wants B, t2 has B but wants A

@Slf4j(topic = "c.TestDeadLock")
public class TestDeadLock {
    
    
    public static void main(String[] args) {
    
    
        test1();
    }

    private static void test1() {
    
    
        Object A = new Object();
        Object B = new Object();
        Thread t1 = new Thread(() -> {
    
    
            synchronized (A) {
    
    
                log.debug("lock A");
                sleep(1);
                synchronized (B) {
    
    
                    log.debug("lock B");
                    log.debug("操作...");
                }
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
    
    
            synchronized (B) {
    
    
                log.debug("lock B");
                sleep(0.5);
                synchronized (A) {
    
    
                    log.debug("lock A");
                    log.debug("操作...");
                }
            }
        }, "t2");
        t1.start();
        t2.start();
    }
}

Deadlock checking method
1. jps locates the process id and jstack id to view the program information
2. jconsole directly views the process information

Livelock
actually means that both threads are changing each other's unlocking conditions, resulting in no lock release, but no blocking. Deadlock means that the lock containing the other party is not released, causing the thread to block.
Solution:
You can change the execution time of threads and let them interleave execution to quickly execute the unlocking condition.
Insert image description here
The starvation problem
is actually that the thread has been unable to acquire (compete for) the lock, resulting in no execution.

13. ReentrantLock

Compared with synchronize
, it can be interrupted
. You can set the acquisition timeout. After the timeout, it will automatically give up acquiring the lock.
Fair lock to prevent starvation problem.
There are many condition variables.

Reentrant
As long as the same thread acquires the same lock, it can be used a second time. (Can be used a second time when not unlocked)

@Slf4j(topic = "c.test22")
public class MyTest22 {
    
    
    public static ReentrantLock lock=new ReentrantLock();
    public static void main(String[] args) {
    
    
        lock.lock();
        try{
    
    
            log.debug("开始进入m1");
            m1();
        }finally {
    
    
            lock.unlock();
        }
    }

    public static void m1(){
    
    
        lock.lock();
        try {
    
    
            log.debug("m1进入");
            m2();
        }finally {
    
    
            lock.unlock();
        }
    }

    public static void m2(){
    
    
        lock.lock();
        try{
    
    
            log.debug("m2进入");
        }finally {
    
    
            lock.unlock();
        }
    }
}

LockInterrupt can be interrupted
. This method can be interrupted by other threads waiting for the lock. If it is a lock, even if it is interrupted, it will have no effect. This interruptability can reduce the occurrence of deadlocks.

@Slf4j(topic = "c.test23")
public class MyTest23 {
    
    
    public static ReentrantLock lock=new ReentrantLock();
    public static void main(String[] args) {
    
    
        Thread t1 = new Thread(() -> {
    
    
            log.debug("上锁");
            lock.lock();
//            try {
    
    
//                lock.lockInterruptibly();
//            } catch (InterruptedException e) {
    
    
//                e.printStackTrace();
//                log.debug("无法获取锁");
//            }
            try {
    
    
                log.debug("获取到锁");
            } finally {
    
    
                lock.unlock();
            }
        }, "t1");
        t1.start();

        lock.lock();
        Sleeper.sleep(1);
        log.debug("帮助t1解锁");
        t1.interrupt();


    }
}

Timeout:
You can use tryLock here to set the timeout for acquiring the lock. If it times out, it will automatically give up acquiring the lock. Instead of keeping it locked

@Slf4j(topic = "c.test24")
public class MyTest24 {
    
    
    public static ReentrantLock lock=new ReentrantLock();

    public static void main(String[] args) {
    
    
        Thread t1 = new Thread(() -> {
    
    
            log.debug("尝试获得锁");

            try {
    
    
                if(!lock.tryLock(2, TimeUnit.SECONDS)){
    
    
                    log.debug("获取锁失败");
                    return ;
                }
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
                log.debug("获取锁失败1");
                return;
            }
            try{
    
    
                log.debug("获取锁成功");
            }finally {
    
    
                lock.unlock();
            }
        }, "t1");

        log.debug("main获取锁");
        lock.lock();
        t1.start();
        Sleeper.sleep(1);
        log.debug("main解锁");
        lock.unlock();
    }
}

To solve the philosopher's problem
,
you can use the tryLock of ReentrantLock. If the attempt fails, you will execute the process below instead of waiting to obtain the lock. If you cannot obtain the lock, the thread will not be blocked.

 @Override
    public void run() {
    
    
        while (true) {
    
    
            if(left.tryLock()){
    
    

                try{
    
    
                    if(right.tryLock()){
    
    
                        try{
    
    
                          eat();
                        }finally {
    
    
                            right.unlock();
                        }
                    }
                }finally {
    
    
                    left.unlock();
                }
            }
        }
    }

Condition variables
define
synchronize to have a lock, and release the lock into waitSet through wait and notify. For ReentrantLock, it is equivalent to having multiple lounge waitSets. After creating the lock, you can create multiple condition variables (multiple rooms). It can be considered that there are multiple lounges in ReentrantLock. Entering different lounges can be processed through different small locks. But in fact, the lock released is still ReentrantLock, and then handed over to others for use. However, different rooms can be controlled through condition variables, allowing the same room but thread area to compete for the lock.

Here is the use of condition variables, but they enter but the thread room is different, the way of operation is the same. The threads of the awakened rooms are not the same.

@Slf4j(topic = "c.Test24")
public class Test224 {
    
    
    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;
    static ReentrantLock ROOM = new ReentrantLock();
    // 等待烟的休息室
    static Condition waitCigaretteSet = ROOM.newCondition();
    // 等外卖的休息室
    static Condition waitTakeoutSet = ROOM.newCondition();

    public static void main(String[] args) {
    
    


        new Thread(() -> {
    
    
            ROOM.lock();
            try {
    
    
                log.debug("有烟没?[{}]", hasCigarette);
                while (!hasCigarette) {
    
    
                    log.debug("没烟,先歇会!");
                    try {
    
    
                        waitCigaretteSet.await();
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
                log.debug("可以开始干活了");
            } finally {
    
    
                ROOM.unlock();
            }
        }, "小南").start();

        new Thread(() -> {
    
    
            ROOM.lock();
            try {
    
    
                log.debug("外卖送到没?[{}]", hasTakeout);
                while (!hasTakeout) {
    
    
                    log.debug("没外卖,先歇会!");
                    try {
    
    
                        waitTakeoutSet.await();
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
                log.debug("可以开始干活了");
            } finally {
    
    
                ROOM.unlock();
            }
        }, "小女").start();

        sleep(1);
        new Thread(() -> {
    
    
            ROOM.lock();
            try {
    
    
                hasTakeout = true;
                waitTakeoutSet.signal();
            } finally {
    
    
                ROOM.unlock();
            }
        }, "送外卖的").start();

        sleep(1);

        new Thread(() -> {
    
    
            ROOM.lock();
            try {
    
    
                hasCigarette = true;
                waitCigaretteSet.signal();
            } finally {
    
    
                ROOM.unlock();
            }
        }, "送烟的").start();
    }

}

Use a lock to fix the order of execution.
The
idea is that both threads need to use this lock, but the t1 thread must run after the t2 thread runs. Then the judgment condition is a boolean. If t2 runs, then modify it and wake up the thread t1. If thread t1 finds that t2 is not running, then wait enters waiting. If it is falsely awakened, it can loop through while to enter wait again.

@Slf4j(topic = "c.25")
public class MyTest25 {
    
    
    static Object lock=new Object();
    static boolean t2Run=false;
    public static void main(String[] args) {
    
    
        Thread t1 = new Thread(() -> {
    
    
            synchronized (lock){
    
    


                try {
    
    
                    while(!t2Run){
    
    
                        lock.wait();
                    }
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                log.debug("1");
            }
        }, "t1");


        Thread t2 = new Thread(() -> {
    
    
            synchronized (lock){
    
    
                log.debug("2");
                //唤醒线程1
                t2Run=true;
                lock.notify();
            }
        }, "t2");

        t1.start();
        t2.start();

    }
}

The second method
is actually to use the park method of LockSupport. If this kind of t1 is executed first, it will park and enter blocking, and then unpark wakes up t1 after t2 is executed. It doesn't matter if t2 is executed first. The unpark of the thread here will change the counter to 1. If t1 first checks the counter and finds that it is 1, then it can continue to execute.

@Slf4j(topic = "c.26")
public class MyTest26 {
    
    
    public static void main(String[] args) {
    
    
        Thread t1 = new Thread(() -> {
    
    
            LockSupport.park();
            log.debug("1");
        }, "t1");

        Thread t2 = new Thread(() -> {
    
    
            log.debug("2");
            LockSupport.unpark(t1);
        }, "t2");

        t1.start();
        t2.start();


    }
}

The idea of ​​printing abc in turn (alternative execution)
synchronized method
is actually to judge whether the flag of this thread is reached. If it is 1, then t1 will be executed. If it is 2, then t2 will be executed. After execution, you need to wake up other threads to check whether it is your own condition. If not, then continue to enter the waiting condition. If the condition is met, acquire the lock and continue execution.

public class MyTest27 {
    
    
    public static void main(String[] args) {
    
    
        WaitNotify1 waitNotify1 = new WaitNotify1(1, 5);
        Thread t1 = new Thread(() -> {
    
    
            waitNotify1.print("a",1,2);
        }, "t1");
        Thread t2 = new Thread(() -> {
    
    
            waitNotify1.print("b",2,3);
        }, "t2");
        Thread t3 = new Thread(() -> {
    
    
            waitNotify1.print("c",3,1);
        }, "t3");

        t1.start();
        t2.start();
        t3.start();
    }
}

class  WaitNotify1{
    
    

    public void print(String s,int waitFlag,int nextFlag){
    
    

        synchronized (this){
    
    
            try {
    
    
                for(int i=0;i<loopNum;i++){
    
    
                    //如果不是1那么就等待
                    while(this.flag!=waitFlag){
    
    
                        this.wait();
                    }
                    System.out.print(s);
                    this.flag=nextFlag;
                    this.notifyAll();
                }

            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }

    }


    private int flag;
    private int loopNum;

    public WaitNotify1(int flag,int loopNum) {
    
    
        this.flag = flag;
        this.loopNum = loopNum;
    }
}

ReentrantLock's idea of ​​​​dealing with sequential execution problems.
The idea of ​​​​execution here is actually to open more condition variables through lock. The condition variables control each rest room. Use condition variables to block the thread and then release it. A, b, and c are opened three times respectively, and then another condition is called after executing one to unlock the other one and continue execution. At first, you need to lock all three threads a, b, and c, and then manually unlock one to start the loop execution.

public class MyTest28 {
    
    
    public static void main(String[] args) {
    
    
        WaitLock waitLock = new WaitLock(5);
        Condition a = waitLock.newCondition();
        Condition b = waitLock.newCondition();
        Condition c = waitLock.newCondition();

        new Thread(()->{
    
    
             waitLock.print("a",a,b);
         },"t1").start();
        new Thread(()->{
    
    
            waitLock.print("b",b,c);
        },"t2").start();
        new Thread(()->{
    
    
            waitLock.print("c",c,a);
        },"t3").start();

        Sleeper.sleep(1);
        waitLock.lock();
        try{
    
    
            //唤醒a
            a.signal();
        }finally {
    
    
            waitLock.unlock();
        }
    }
}

@Slf4j(topic = "c.lock")
class WaitLock extends ReentrantLock{
    
    

    private int loopNum;

    public WaitLock(int loopNum) {
    
    
        this.loopNum = loopNum;
    }

    public void print(String str,Condition cur,Condition next){
    
    
        for(int i=0;i<loopNum;i++){
    
    
            lock();
            try{
    
    
                cur.await();
                System.out.print(str);
                next.signal();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                unlock();
            }

        }
    }
}

LockSupport execution idea
In fact, here you can directly use park to block three threads, and then manually start one thread. If you need to start the blocked thread, you need to unpark(t), which means you need to pass in the corresponding thread parameters and wake up the corresponding thread. After t1 is executed, wake up t2, and after t2 is executed, wake up t3. Then the method called at this time must pass in the corresponding thread parameters. Moreover, thread parameters can be shared and placed in the method area. If they are placed on the main thread, they cannot be seen.

@Slf4j(topic = "c.test29")
public class MyTest29 {
    
    
    static Thread t1;
    static Thread t2;
    static Thread t3;
    public static void main(String[] args) {
    
    
        WaitPark waitPark = new WaitPark(5);
        t1=new Thread(()->{
    
    
            waitPark.print("a",t2);
        },"t1");
        t2=new Thread(()->{
    
    
            waitPark.print("b",t3);
        },"t2");
        t3=new Thread(()->{
    
    
            waitPark.print("c",t1);
        },"t3");

        t1.start();
        t2.start();
        t3.start();
        Sleeper.sleep(1);
        LockSupport.unpark(t1);
    }
}
@Slf4j(topic = "c.lock")
class WaitPark{
    
    
    private int loopNum;

    public WaitPark(int loopNum) {
    
    
        this.loopNum = loopNum;
    }



    public void print(String str,Thread t){
    
    
        for(int i=0;i<loopNum;i++){
    
    
            LockSupport.park();
            log.debug(str);
            LockSupport.unpark(t);
        }
    }
}

Guess you like

Origin blog.csdn.net/weixin_45841848/article/details/132614018