Skip to content

Commit ec08087

Browse files
committed
docs(thread): add note about wait and notify
1 parent fc1bec2 commit ec08087

1 file changed

Lines changed: 271 additions & 0 deletions

File tree

java/basic/java-thread.md

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
* [进程与线程](#进程与线程)
44
* [单线程与多线程](#单线程与多线程)
55
* [实现线程的4中方式](#实现线程的4中方式)
6+
* [thread.start()和runnable.run()的区别](#thread.start()和runnable.run()的区别)
7+
* [Thread和Runnable的异同](#Thread和Runnable的异同)
8+
* [线程的基本操作](#线程的基本操作)
9+
* [线程的优先级与守护线程](#线程的优先级与守护线程)
10+
* [线程的状态与转换](#线程的状态与转换)
11+
* [synchronized关键字](#synchronized关键字)
12+
* [实例锁与全局锁](#实例锁与全局锁)
13+
* [wait和notify](#wait和notify)
614

715
## 基础概念
816

@@ -212,6 +220,269 @@ public void foo() {
212220
2. 当一个线程访问一个对象的synchronized方法或者synchronized代码块时,其他线程对该对象的非synchronized方法的访问将不会被阻塞。
213221
3. 当一个线程访问一个对象的synchronized方法或者synchronized代码块时,其他线程对该对象的其他synchronized方法或代码块的访问将会被阻塞。
214222

223+
### 实例锁与全局锁
224+
> 实例锁:锁在某一个实例对象上。如果该类是单例,那么该锁也具有全局锁的概念。实例锁对应的就是synchronized关键字。
225+
```java
226+
synchronized(this) synchronized(obj)
227+
public synchronized void foo()
228+
```
229+
> 全局锁:该锁针对的是类,无论实例多少个对象,那么线程都共享该锁。全局锁对应的就是static synchronized(或者是锁在该类的class或者classloader对象上)。
230+
```java
231+
synchronized(XXXClass.class)
232+
public static synchronized void foo()
233+
```
234+
> 例子
235+
```java
236+
pulbic class Something {
237+
public synchronized void isSyncA(){}
238+
public synchronized void isSyncB(){}
239+
public static synchronized void cSyncA(){}
240+
public static synchronized void cSyncB(){}
241+
}
242+
```
243+
假设,Something有两个实例x和y。分析下面4组表达式获取的锁的情况。
244+
1. x.isSyncA()与x.isSyncB() 不能同时访问。实例锁,访问两个同步方法的对象是同一个对象x
245+
2. x.isSyncA()与y.isSyncA() 能同时访问。实例锁,访问同一个同步方法的对象是两个不同的对象,实例锁不是同一个
246+
3. x.cSyncA()与y.cSyncB() 不能同时访问。因为cSyncA()和cSyncB()都是static类型,x.cSyncA()相当于Something.isSyncA(),y.cSyncB()相当于Something.isSyncB(),因此它们共用一个同步锁,不能被同时反问。
247+
4. x.isSyncA()与Something.cSyncA() 可以被同时访问。因为isSyncA()是实例方法,x.isSyncA()使用的是对象x的锁;而cSyncA()是静态方法,Something.cSyncA()可以理解对使用的是“类的锁”。因此,它们是可以被同时访问的。
248+
```java
249+
public class SynchronizedLockExample {
250+
251+
public static void main(String[] args) {
252+
SynchronizedLock x = new SynchronizedLock();
253+
// x.syncA()与x.syncB()
254+
new Thread(()-> {
255+
try {
256+
x.syncA();
257+
} catch (InterruptedException e) {
258+
e.printStackTrace();
259+
}
260+
}, "Threadx ").start();
261+
new Thread(()-> {
262+
try {
263+
x.syncB();
264+
} catch (InterruptedException e) {
265+
e.printStackTrace();
266+
}
267+
}, "Thready ").start();
268+
/** 实例锁。不能同时访问
269+
* Threadx 0
270+
* Threadx 1
271+
* Threadx 2
272+
* Thready 0
273+
* Thready 1
274+
* Thready 2
275+
*/
276+
// x.syncA()与y.syncA()
277+
SynchronizedLock y = new SynchronizedLock();
278+
SynchronizedLock y2 = new SynchronizedLock();
279+
new Thread(() -> {
280+
try {
281+
y.syncA();
282+
} catch (InterruptedException e) {
283+
e.printStackTrace();
284+
}
285+
}, "Thready1").start();
286+
new Thread(() -> {
287+
try {
288+
y2.syncA();
289+
} catch (InterruptedException e) {
290+
e.printStackTrace();
291+
}
292+
}, "Thready2").start();
293+
/**实例锁。可以同时访问,实例不是同一个对象锁
294+
* Thready10
295+
* Thready20
296+
* Thready21
297+
* Thready11
298+
* Thready22
299+
* Thready12
300+
*/
301+
// x.syncC()与y.syncD()
302+
SynchronizedLock x1 = new SynchronizedLock();
303+
SynchronizedLock y3 = new SynchronizedLock();
304+
new Thread(()-> {
305+
try {
306+
x1.syncC();
307+
} catch (InterruptedException e) {
308+
e.printStackTrace();
309+
}
310+
}, "Threadx1 ").start();
311+
new Thread(()-> {
312+
try {
313+
y3.syncD();
314+
} catch (InterruptedException e) {
315+
e.printStackTrace();
316+
}
317+
}, "Thready3 ").start();
318+
/** 全局锁。不能同时访问,static synchronized修饰的方法是全局静态的,与实例无关
319+
* Threadx1 0
320+
* Threadx1 1
321+
* Threadx1 2
322+
* Thready3 0
323+
* Thready3 1
324+
* Thready3 2
325+
*/
326+
// x.syncA与SynchronizedLock.syncD
327+
SynchronizedLock x3 = new SynchronizedLock();
328+
new Thread(()-> {
329+
try {
330+
x3.syncA();
331+
} catch (InterruptedException e) {
332+
e.printStackTrace();
333+
}
334+
}, "Theradx3").start();
335+
new Thread(() -> {
336+
try {
337+
SynchronizedLock.syncD();
338+
} catch (InterruptedException e) {
339+
e.printStackTrace();
340+
}
341+
}, "Threadstatic ").start();
342+
/** 可以同时访问。x.syncA是实例锁,SynchronizedLock.syncD是全局锁
343+
* Theradx30
344+
* Threadstatic 0
345+
* Theradx31
346+
* Threadstatic 1
347+
* Theradx32
348+
* Threadstatic 2
349+
*/
350+
}
351+
}
352+
353+
class SynchronizedLock {
354+
355+
public synchronized void syncA() throws InterruptedException {
356+
for (int i = 0; i < 3; i++) {
357+
System.out.println(Thread.currentThread().getName() + i);
358+
Thread.sleep(1000);
359+
}
360+
}
361+
362+
public synchronized void syncB() throws InterruptedException {
363+
for (int i = 0; i < 3; i++) {
364+
System.out.println(Thread.currentThread().getName() + i);
365+
Thread.sleep(1000);
366+
}
367+
}
368+
369+
public static synchronized void syncC() throws InterruptedException {
370+
for (int i = 0; i < 3; i++) {
371+
System.out.println(Thread.currentThread().getName() + i);
372+
Thread.sleep(1000);
373+
}
374+
}
375+
376+
public static synchronized void syncD() throws InterruptedException {
377+
for (int i = 0; i < 3; i++) {
378+
System.out.println(Thread.currentThread().getName() + i);
379+
Thread.sleep(1000);
380+
}
381+
}
382+
}
383+
```
384+
385+
### wait和notify
386+
> wait, notify, notifyAll
387+
在Object.java中,定义了wait(), notify()和notifyAll()等接口。wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。而notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。
388+
389+
Object类中关于等待/唤醒的API详细信息如下:
390+
* notify() -- 唤醒在此对象监视器上等待的单个线程。
391+
* notifyAll() -- 唤醒在此对象监视器上等待的所有线程。
392+
* wait() -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。
393+
* wait(long timeout) -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
394+
* wait(long timeout, int nanos) -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。
395+
```java
396+
public class NotifyExample {
397+
398+
public static void main(String[] args) {
399+
Notify notify = new Notify();
400+
new Thread(()-> {
401+
synchronized (notify) {
402+
while (notify.flag) {
403+
System.out.println("User A");
404+
try {
405+
notify.wait();
406+
} catch (InterruptedException e) {
407+
e.printStackTrace();
408+
}
409+
}
410+
}
411+
notify.call();
412+
synchronized (notify) {
413+
notify.notifyAll();
414+
}
415+
}, "User A").start();
416+
417+
new Thread(()-> {
418+
synchronized (notify) {
419+
while (notify.flag) {
420+
System.out.println("User B");
421+
try {
422+
notify.wait();
423+
} catch (InterruptedException e) {
424+
e.printStackTrace();
425+
}
426+
}
427+
}
428+
notify.call();
429+
synchronized (notify) {
430+
notify.notifyAll();
431+
}
432+
}, "User B").start();
433+
/**
434+
* Begin to call
435+
* User A calling 0%
436+
* User B
437+
* User A calling 50%
438+
* User A calling 100%
439+
* End to call
440+
* Begin to call
441+
* User B calling 0%
442+
* User B calling 50%
443+
* User B calling 100%
444+
* End to call
445+
*/
446+
}
447+
}
448+
449+
class Notify {
450+
public boolean flag = false;
451+
public void call() {
452+
flag = true;
453+
System.out.println("Begin to call");
454+
for (int i = 0; i < 101; i+=50) {
455+
System.out.println(Thread.currentThread().getName() + " calling " + i + "%");
456+
try {
457+
Thread.sleep(100);
458+
} catch (InterruptedException e) {
459+
e.printStackTrace();
460+
}
461+
}
462+
System.out.println("End to call");
463+
flag = false;
464+
}
465+
}
466+
```
467+
> 注意事项
468+
* “当前线程”在调用wait()时,必须拥有该对象的同步锁。该线程调用wait()之后,会释放该锁;然后一直等待直到“其它线程”调用对象的同步锁的notify()或notifyAll()方法。然后,该线程继续等待直到它重新获取“该对象的同步锁”,然后就可以接着运行。, synchronized(obj),否则会出现`java.lang.IllegalMonitorStateException`
469+
* 调用notify时也需要获得该对象的“同步锁”,jdk中的注释:
470+
```md
471+
This method should only be called by a thread that is the owner of this object's monitor. A thread becomes the owner of the object's monitor in one of three ways:
472+
1. By executing a synchronized instance method of that object. 通过获得该对象的同步锁
473+
2. By executing the body of a {@code synchronized} statement that synchronizes on the object. 在该对象的同步代码块中执行
474+
3. For objects of type {@code Class,} by executing a synchronized static method of that class. 通过执行全局锁的方法
475+
```
476+
* Only one thread at a time can own an object's monitor.
477+
478+
> 为什么notify(), wait()等函数定义在Object中,而不是Thread中
479+
Object中的wait(), notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作。
480+
wait()会使“当前线程”等待,因为线程进入等待状态,所以线程应该释放它锁持有的“同步锁”,否则其它线程获取不到该“同步锁”而无法运行!
481+
OK,线程调用wait()之后,会释放它锁持有的“同步锁”;而且,根据前面的介绍,我们知道:等待线程可以被notify()或notifyAll()唤醒。现在,请思考一个问题:notify()是依据什么唤醒等待线程的?或者说,wait()等待线程和notify()之间是通过什么关联起来的?答案是:依据“对象的同步锁”。
482+
负责唤醒等待线程的那个线程(我们称为“唤醒线程”),它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。虽然,等待线程被唤醒;但是,它不能立刻执行,因为唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放了“对象的同步锁”之后,等待线程才能获取到“对象的同步锁”进而继续运行。
483+
484+
总之,notify(), wait()依赖于“同步锁”,而“同步锁”是对象锁持有,并且每个对象有且仅有一个!这就是为什么notify(), wait()等函数定义在Object类,而不是Thread类中的原因。
485+
215486
### 生产者消费者问题
216487

217488
https://www.cnblogs.com/skywang12345/

0 commit comments

Comments
 (0)