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
- Grasp the synchronized lock object syntax
- Master the synchronzied loading member method and static method syntax
- Master the wait/notify synchronization method
- Use lock mutual exclusion to solve the thread safety problem of critical section
- 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
- Mutual exclusion: Use synchronized or Lock to achieve mutual exclusion of shared resources
- 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