4. Management of shared model

Contents of this chapter

  • Sharing issues
  • synchronized
  • Thread safety analysis
  • Monitor
  • wait/notify
  • Thread state transition
  • Activeness
  • Lock
     

Principle of wait / notify

  • The Owner thread finds that the condition is not met, and calls the wait method to enter the WaitSet state and change to the WAITING state
  • BLOCKED and WAITING threads are in a blocked state and do not occupy CPU time slices
  • The BLOCKED thread will wake up when the Owner thread releases the lock
  • The WAITING thread will wake up when the Owner thread calls notify or notifyAll, but the wake-up does not mean that the lock will be obtained immediately, and it still needs to enter the EntryList to compete again

API introduction

obj.wait() Let the thread that enters the object monitor wait for
obj.notify() on the object that is waiting for waitSet, pick one of the threads that are waiting for waitSet on the object to wake up
obj.notifyAll() Let all the threads that are waiting for waitSet on the object wake up

They are all means of collaboration between threads, and they are all methods of the Object object. The lock of this object must be obtained before these methods can be called

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("其它代码....");
}
}).start();
new Thread(() -> {
synchronized (obj) {
log.debug("执行....");
try {
obj.wait(); // 让线程在obj上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("其它代码....");
}
}).start();
// 主线程两秒后执行
sleep(2);
log.debug("唤醒 obj 上其它线程");
synchronized (obj) {
obj.notify(); // 唤醒obj上一个线程
// obj.notifyAll(); // 唤醒obj上所有等待线程
}
}

a result of notify

20:00:53.096 [Thread-0] c.TestWaitNotify - 执行....
20:00:53.099 [Thread-1] c.TestWaitNotify - 执行....
20:00:55.096 [main] c.TestWaitNotify - 唤醒 obj 上其它线程
20:00:55.096 [Thread-0] c.TestWaitNotify - 其它代码....

the result of notifyAll

19:58:15.457 [Thread-0] c.TestWaitNotify - 执行....
19:58:15.460 [Thread-1] c.TestWaitNotify - 执行....
19:58:17.456 [main] c.TestWaitNotify - 唤醒 obj 上其它线程
19:58:17.456 [Thread-1] c.TestWaitNotify - 其它代码....
19:58:17.456 [Thread-0] c.TestWaitNotify - 其它代码....

The wait() method releases the lock of the object and enters the WaitSet waiting area, so that other threads have the opportunity to acquire the lock of the object. Unlimited wait until
notify
wait(long n) Time-limited wait, wait until n milliseconds later, or be notified

4.8 Correct posture of wait notify

Take a look before you start

The difference between sleep(long n) and wait(long n)
1) sleep is a Thread method, while wait is an Object method 2) sleep does not need to be used in conjunction with synchronized, but wait needs to be used with synchronized 3) sleep is in sleep At the same time, the object lock will not be released, but wait will release the object lock while waiting. 4) Their status is TIMED_WAITING

synchronized(lock) {
while(条件不成立) {
lock.wait();
}
// 干活
}
//另一个线程
synchronized(lock) {
lock.notifyAll();
}

4.9 Park & Unpark

Basic use

They are methods in the LockSupport class

// 暂停当前线程
LockSupport.park();
// 恢复某个线程的运行
LockSupport.unpark(暂停线程对象)

Park first and then unpark

Thread t1 = new Thread(() -> {
log.debug("start...");
sleep(1);
log.debug("park...");
LockSupport.park();
log.debug("resume...");
},"t1");
t1.start();
sleep(2);
log.debug("unpark...");
LockSupport.unpark(t1);

Output

18:42:52.585 c.TestParkUnpark [t1] - start...
18:42:53.589 c.TestParkUnpark [t1] - park...
18:42:54.583 c.TestParkUnpark [main] - unpark...
18:42:54.583 c.TestParkUnpark [t1] - resume...

First unpark then park

Thread t1 = new Thread(() -> {
log.debug("start...");
sleep(2);
log.debug("park...");
LockSupport.park();
log.debug("resume...");
}, "t1");
t1.start();
sleep(1);
log.debug("unpark...");
LockSupport.unpark(t1);

Output

18:43:50.765 c.TestParkUnpark [t1] - start...
18:43:51.764 c.TestParkUnpark [main] - unpark...
18:43:52.769 c.TestParkUnpark [t1] - park...
18:43:52.769 c.TestParkUnpark [t1] - resume...

Features

Compared with Object's wait & notify

  • wait, notify and notifyAll must be used together with Object Monitor, while park and unpark do not have to
  • park & ​​unpark uses thread as a unit to [block] and [wake up] threads, while notify can only wake up one waiting thread randomly, notifyAll wakes up all waiting threads, which is not so [precise]
  • park & ​​unpark can unpark first, but wait & notify cannot notify first

Principle of park & ​​unpark

Each thread has its own Parker object, which is composed of three parts: _counter, _cond and _mutex. To make a metaphor, a
thread is like a traveler, and Parker is like a backpack he carries with him. The condition variables are like a tent in a backpack. _counter is like
the spare dry food in the backpack (0 is exhausted, 1 is sufficient).
Calling park depends on whether you need to stop and rest.
If the spare dry food is exhausted, then go to the tent and rest.
If the spare dry food is sufficient, then there is no need to stay. Keep going.
Calling unpark is like making enough dry food.
If the thread is still in the tent at this time, wake it up and let him move on.
If the thread is still running at this time, the next time he calls park, it will only consume the spare dry food and don’t need to stay. Keep going.
Because the backpack space is limited, calling unpark multiple times will only add a spare dry food.

1. The current thread calls the Unsafe.park() method
2. Check _counter, this case is 0, at this time, get the _mutex mutex
3. Thread enters the _cond condition variable blocking
4. Set _counter = 0

1. Call Unsafe.unpark(Thread_0) method and set _counter to 1
2. Wake up Thread_0 in the condition variable of _cond
3. Thread_0 to resume operation
4. Set _counter to 0

1. Call the Unsafe.unpark(Thread_0) method and set the _counter to 1
2. The current thread calls the Unsafe.park() method
3. Check the _counter, this situation is 1, then the thread does not need to be blocked, continue to run
4. Set_ counter is 0

4.10 Re-understanding thread state transition

 

4.11 Multiple locks

Multiple irrelevant locks

A big room has two functions: sleeping and studying, which are not related to each other.
Now Xiao Nan wants to study and the little girl wants to sleep, but if only one room (one object lock) is used, the concurrency is very low. The
solution is to prepare multiple rooms (multiple object locks).
For example

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

carried out

BigRoom bigRoom = new BigRoom();
new Thread(() -> {
bigRoom.compute();
},"小南").start();
new Thread(() -> {
bigRoom.sleep();
},"小女").start();

A certain result

12:13:54.471 [小南] c.BigRoom-study 1 hour
12:13:55.476 [小女] c.BigRoom-sleeping 2 hours

Improve

class BigRoom {
private final Object studyRoom = new Object();
private final Object bedRoom = new Object();
public void sleep() {
synchronized (bedRoom) {
log.debug("sleeping 2 小时");
Sleeper.sleep(2);
}
}
public void study() {
synchronized (studyRoom) {
log.debug("study 1 小时");
Sleeper.sleep(1);
}
}
}

The result of a certain execution

12:15:35.069 [小南] c.BigRoom-study 1 hour
12:15:35.069 [小女] c.BigRoom-sleeping 2 hours

Break down the granularity of the lock

  • The advantage is that it can enhance concurrency
  • Disadvantage, if a thread needs to acquire multiple locks at the same time, deadlock is prone to occur

4.12 Liveness

Deadlock

There is a situation: a thread needs to acquire multiple locks at the same time, and deadlock is prone to occur at this time.
t1 thread acquires the lock of object A, and then wants to acquire the lock of object B. t2 thread acquires the lock of object B, and then wants to acquire the object of A Lock case

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();

result

12:22:06.962 [t2] c.TestDeadLock - lock B
12:22:06.962 [t1] c.TestDeadLock - lock A

Philosopher's dining problem

There are five philosophers sitting around the round table.

  • They only do two things, thinking and eating, thinking about eating for a while, and then thinking after eating.
  • Two chopsticks are used when eating. There are 5 chopsticks on the table. Each philosopher has one chopstick on his left and right hand.
  • If the chopsticks are held by the people around you, you have to wait

Chopsticks

class Chopstick {
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" + name + '}';
}
}

Philosopher

class Philosopher extends Thread {
Chopstick left;
Chopstick right;
public Philosopher(String name, Chopstick left, Chopstick right) {
super(name);
this.left = left;
this.right = right;
}
private void eat() {
log.debug("eating...");
Sleeper.sleep(1);
}
@Override
public void run() {
while (true) {
// 获得左手筷子
synchronized (left) {
// 获得右手筷子
synchronized (right) {
// 吃饭
eat();
}
// 放下右手筷子
}
// 放下左手筷子
}
}
}

Dining

Chopstick c1 = new Chopstick("1");
Chopstick c2 = new Chopstick("2");
Chopstick c3 = new Chopstick("3");
Chopstick c4 = new Chopstick("4");
Chopstick c5 = new Chopstick("5");
new Philosopher("苏格拉底", c1, c2).start();
new Philosopher("柏拉图", c2, c3).start();
new Philosopher("亚里士多德", c3, c4).start();
new Philosopher("赫拉克利特", c4, c5).start();
new Philosopher("阿基米德", c5, c1).start();

After a short period of time, it will not continue.

12:33:15.575 [Socrates] c.Philosopher-eating...
12:33:15.575 [Aristotle] c.Philosopher-eating...
12:33:16.580 [Archimedes] c .Philosopher-eating...
12:33:17.580 [Archimedes] c.Philosopher-eating...
// stuck here, not running down

Use jconsole to detect deadlocks and find

-------------------------------------------------------------------------
名称: 阿基米德
状态: cn.itcast.Chopstick@1540e19d (筷子1) 上的BLOCKED, 拥有者: 苏格拉底
总阻止数: 2, 总等待数: 1
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
- 已锁定 cn.itcast.Chopstick@6d6f6e28 (筷子5)
-------------------------------------------------------------------------
名称: 苏格拉底
状态: cn.itcast.Chopstick@677327b6 (筷子2) 上的BLOCKED, 拥有者: 柏拉图
总阻止数: 2, 总等待数: 1
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
- 已锁定 cn.itcast.Chopstick@1540e19d (筷子1)
-------------------------------------------------------------------------
名称: 柏拉图
状态: cn.itcast.Chopstick@14ae5a5 (筷子3) 上的BLOCKED, 拥有者: 亚里士多德
总阻止数: 2, 总等待数: 0
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
- 已锁定 cn.itcast.Chopstick@677327b6 (筷子2)
-------------------------------------------------------------------------
名称: 亚里士多德
状态: cn.itcast.Chopstick@7f31245a (筷子4) 上的BLOCKED, 拥有者: 赫拉克利特
总阻止数: 1, 总等待数: 1
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
- 已锁定 cn.itcast.Chopstick@14ae5a5 (筷子3)
-------------------------------------------------------------------------
名称: 赫拉克利特
状态: cn.itcast.Chopstick@6d6f6e28 (筷子5) 上的BLOCKED, 拥有者: 阿基米德
总阻止数: 2, 总等待数: 0
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
- 已锁定 cn.itcast.Chopstick@7f31245a (筷子4)发现

This kind of thread does not end as expected, and the execution can not continue, classified as a [liveness] problem. In addition to deadlocks, there are two cases of livelock and hungry.

Livelock

Livelock occurs when two threads change the end conditions of each other, and no one can end in the end, for example

public class TestLiveLock {
static volatile int count = 10;
static final Object lock = new Object();
public static void main(String[] args) {
new Thread(() -> {
// 期望减到 0 退出循环
while (count > 0) {
sleep(0.2);
count--;
log.debug("count: {}", count);
}
}, "t1").start();
new Thread(() -> {
// 期望超过 20 退出循环
while (count < 20) {
sleep(0.2);
count++;
log.debug("count: {}", count);
}
}, "t2").start();
}
}

hunger

In many tutorials, hunger is defined as a thread because its priority is too low, it cannot be scheduled and executed by the CPU, nor can it end. The hunger situation is not easy to demonstrate. When talking about read-write locks, it will involve hunger problems.
Let me talk about my encounters. An example of thread starvation, let’s take a look at the use of sequential locking to solve the previous deadlock problem

Sequential locking solution

4.13 ReentrantLock

Compared with synchronized, it has the following characteristics:
interruptible,
timeout
can be set, fair lock,
multiple condition variables
are supported, like synchronized, both support reentrant
basic syntax

// 获取锁
reentrantLock.lock();
try {
// 临界区
} finally {
// 释放锁
reentrantLock.unlock();
}

Reentrant

Reentrant means that if the same thread acquires the lock for the first time, then because it is the owner of the lock, it has the right to acquire the lock again.
If it is a non-reentrant lock, then when the lock is acquired for the second time, I will be blocked by the lock

static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
method1();
}
public static void method1() {
lock.lock();
try {
log.debug("execute method1");
method2();
} finally {
lock.unlock();
}
}
public static void method2() {
lock.lock();
try {
log.debug("execute method2");
method3();
} finally {
lock.unlock();
}
}
public static void method3() {
lock.lock();
try {
log.debug("execute method3");
} finally {
lock.unlock();
}
}

Output

17:59:11.862 [main] c.TestReentrant - execute method1
17:59:11.865 [main] c.TestReentrant - execute method2
17:59:11.865 [main] c.TestReentrant - execute method3

Can be interrupted

Example

ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("启动...");
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("等锁的过程中被打断");
return;
}
try {
log.debug("获得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
try {
sleep(1);
t1.interrupt();
log.debug("执行打断");
} finally {
lock.unlock();
}

Output

18:02:40.520 [main] c.TestInterrupt - 获得了锁
18:02:40.524 [t1] c.TestInterrupt - 启动...
18:02:41.530 [main] c.TestInterrupt - 执行打断
java.lang.InterruptedException
at
java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchr
onizer.java:898)
at
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchron
izer.java:1222)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at cn.itcast.n4.reentrant.TestInterrupt.lambda$main$0(TestInterrupt.java:17)
at java.lang.Thread.run(Thread.java:748)
18:02:41.532 [t1] c.TestInterrupt - 等锁的过程中被打断

Note that if it is in non-interruptible mode, then even if interrupt is used, the wait will not be interrupted

ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("启动...");
lock.lock();
try {
log.debug("获得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
try {
sleep(1);
t1.interrupt();
log.debug("执行打断");
sleep(1);
} finally {
log.debug("释放了锁");
lock.unlock();
}

Output

18:06:56.261 [main] c.TestInterrupt - 获得了锁
18:06:56.265 [t1] c.TestInterrupt - 启动...
18:06:57.266 [main] c.TestInterrupt - 执行打断 // 这时 t1 并没有被真正打断, 而是仍继续等待锁
18:06:58.267 [main] c.TestInterrupt - 释放了锁
18:06:58.267 [t1] c.TestInterrupt - 获得了锁

Lock timeout

Fail immediately

ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("启动...");
if (!lock.tryLock()) {
log.debug("获取立刻失败,返回");
return;
}
try {
log.debug("获得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("获得了锁")
t1.start();
try {
sleep(2);
} finally {
lock.unlock();
}

Output

18:15:02.918 [main] c.TestTimeout-Acquired the lock
18:15:02.921 [t1] c.TestTimeout-Start...
18:15:02.921 [t1] c.TestTimeout-Failed to acquire immediately, return failed with
timeout
 

ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("启动...");
try {
if (!lock.tryLock(1, TimeUnit.SECONDS)) {
log.debug("获取等待 1s 后失败,返回");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
log.debug("获得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
try {
sleep(2);
} finally {
lock.unlock();
}

Output

18:19:40.537 [main] c.TestTimeout - 获得了锁
18:19:40.544 [t1] c.TestTimeout - 启动...
18:19:41.547 [t1] c.TestTimeout - 获取等待 1s 后失败,返回

Use tryLock to solve the philosopher's dining problem
 

class Chopstick extends ReentrantLock {
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" + name + '}';
}
}

 

class Philosopher extends Thread {
Chopstick left;
Chopstick right;
public Philosopher(String name, Chopstick left, Chopstick right) {
super(name);
this.left = left;
this.right = right;
}
@Override
public void run() {
while (true) {
// 尝试获得左手筷子
if (left.tryLock()) {
try {
// 尝试获得右手筷子
if (right.tryLock()) {
try {
eat();
} finally {
right.unlock();
}
}
} finally {
left.unlock();
}
}
}
}
private void eat() {
log.debug("eating...");
Sleeper.sleep(1);
}
}

Fair lock

ReentrantLock is unfair by default

ReentrantLock lock = new ReentrantLock(false);
lock.lock();
for (int i = 0; i < 500; i++) {
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " running...");
} finally {
lock.unlock();
}
}, "t" + i).start();
}
// 1s 之后去争抢锁
Thread.sleep(1000);
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " start...");
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " running...");
} finally {
lock.unlock();
}
}, "强行插入").start();
lock.unlock();

Forcibly insert, there is a chance to output in the middle
Note: This experiment may not always be able to reproduce

Condition variable

There are also condition variables in synchronized, which is the waitSet lounge when we talk about the principle. When the conditions are not met, enter waitSet and wait for
ReentrantLock. The condition variable is more powerful than synchronized in that it supports multiple condition variables, which is like

  • Synchronized is that those threads that do not meet the conditions are waiting for news in a lounge
  • And ReentrantLock supports multiple lounges, there are lounges dedicated to waiting for cigarettes, lounges dedicated to waiting for breakfast, and wake up by the lounge when you wake up.

Key points of use:

  • Need to acquire lock before await
  • After await is executed, the lock will be released and enter the conditionObject to wait
  • The await thread is awakened (or interrupted, or timed out) to re-compete for the lock lock
  • After the competition lock is successfully locked, continue execution after await

example:

static ReentrantLock lock = new ReentrantLock();
static Condition waitCigaretteQueue = lock.newCondition();
static Condition waitbreakfastQueue = lock.newCondition();
static volatile boolean hasCigrette = false;
static volatile boolean hasBreakfast = false;
public static void main(String[] args) {
new Thread(() -> {
try {
lock.lock();
while (!hasCigrette) {
try {
waitCigaretteQueue.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("等到了它的烟");
} finally {
lock.unlock();
}
}).start();
new Thread(() -> {
try {
lock.lock();
while (!hasBreakfast) {
try {
waitbreakfastQueue.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("等到了它的早餐");
} finally {
lock.unlock();
}
}).start();
sleep(1);
sendBreakfast();
sleep(1);
sendCigarette();
}
private static void sendCigarette() {
lock.lock();
try {
log.debug("送烟来了");
hasCigrette = true;
waitCigaretteQueue.signal();
} finally {
lock.unlock();
}
}
private static void sendBreakfast() {
lock.lock();
try {
log.debug("送早餐来了");
hasBreakfast = true;
waitbreakfastQueue.signal();
} finally {
lock.unlock();
}
}

Output

18:52:27.680 [main] c.TestCondition-Delivery of breakfast
18:52:27.682 [Thread-1] c.TestCondition-Waited for its breakfast
18:52:28.683 [main] c.TestCondition-Delivery of smoke a
18: 52: 28.683 [Thread- 0] c.TestCondition - smoke until it

chapter summary

What we need to focus on in this chapter is

  • Analyze which code fragments belong to the critical section when multi-threaded access to shared resources
  • Use synchronized mutual exclusion to solve the thread safety problem of critical section
  1. Grasp the synchronized lock object syntax
  2. Master the synchronzied loading member method and static method syntax
  3. Master the wait/notify synchronization method
  • Use lock mutual exclusion to solve the thread safety problem of critical section
  1. Master the use details of lock: interruptible, lock timeout, fair lock, condition variable
  • Learn to analyze the thread safety of variables and master the use of common thread safety classes
  • Understand thread activity issues: deadlock, livelock, starvation
  • Application aspect
  1. Mutual exclusion: Use synchronized or Lock to achieve mutual exclusion of shared resources
  2. Synchronization: use wait/notify or Lock condition variables to achieve the effect of inter-thread communication

Principle aspect

  • monitor、synchronized 、wait/notify 原理
  • Advanced principle of synchronized
  • park & ​​unpark principle

Model aspect

  • Protective pause in sync mode
  • Producer and consumer in asynchronous mode
  • Sequence control of synchronous mode
     

Guess you like

Origin blog.csdn.net/nmjhehe/article/details/109631200