线程Thread是一个程序的多个执行路径,执行调度的单位,依托于进程存在。 线程不仅可以共享进程的内存,而且还拥有一个属于自己的内存空间,这段内存空间也叫做线程栈,是在建立线程时由系统分配的,主要用来保存线程内部所使用的数据,如线程执行函数中所定义的变量。
Java中的多线程是一种抢占机制而不是分时机制。抢占机制指的是有多个线程处于可运行状态,但是只允许一个线程在运行,他们通过竞争的方式抢占CPU。
下面介绍一些常用的Thread方法。
join()
:静态方法,返回对当前正在执行的线程对象的引用在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。
Join方法实现是通过wait(小提示:Object 提供的方法)。 当main线程调用t.join时候,main线程会获得线程对象t的锁(wait 意味着拿到该对象的锁),调用该对象的wait(等待时间),直到该对象唤醒main线程 ,比如退出后。这就意味着main 线程调用t.join时,必须能够拿到线程t对象的锁。
join() 一共有三个重载版本,分别是无参、一个参数、两个参数:
public final void join() throws InterruptedException; //无参数的join()等价于join(0),作用是一直等待该线程死亡 public final synchronized void join(long millis) throws InterruptedException; //最多等待该线程死亡millis毫秒 public final synchronized void join(long millis, int nanos) throws InterruptedException; //最多等待该线程死亡millis毫秒加nanos纳秒
(1) 三个方法都被final修饰,无法被子类重写。
(2) join(long), join(long, long) 是synchronized method,同步的对象是当前线程实例。
(2) 无参版本和两个参数版本最终都调用了一个参数的版本。
(3) join() 和 join(0) 是等价的,表示一直等下去;join(非0)表示等待一段时间。
从源码可以看到 join(0) 调用了Object.wait(0),其中Object.wait(0) 会一直等待,直到被notify/中断才返回。
while(isAlive())是为了防止子线程伪唤醒(spurious wakeup),只要子线程没有TERMINATED的,父线程就需要继续等下去。
(4) join() 和 sleep() 一样,可以被中断(被中断时,会抛出 InterrupptedException 异常);不同的是,join() 内部调用了 wait(),会出让锁,而 sleep() 会一直保持锁。
示例如下:
1 class TestThread extends Thread { 2 public TestThread() { 3 super("[TestThread] Thread"); 4 } 5 6 @Override 7 public void run() { 8 String threadName = Thread.currentThread().getName(); 9 System.out.println(threadName + " start."); 10 try { 11 for (int i = 0; i < 5; i++) { 12 System.out.println(threadName + " loop at " + i); 13 Thread.sleep(1000); 14 } 15 System.out.println(threadName + " end."); 16 } catch (Exception e) { 17 System.out.println("Exception from " + threadName + ".run"); 18 } 19 } 20 } 21 22 public class JoinDemo { 23 public static void main(String[] args) { 24 String threadName = Thread.currentThread().getName(); 25 System.out.println(threadName + " start."); 26 TestThread bt = new TestThread(); 27 try { 28 bt.start(); 29 bt.join(); 30 Thread.sleep(2000); 31 } catch (Exception e) { 32 System.out.println("Exception from main"); 33 } 34 System.out.println(threadName + " end!"); 35 } 36 }
结果如下,主线程会等待子线程执行结束。
>> main start. [TestThread] Thread start. [TestThread] Thread loop at 0 [TestThread] Thread loop at 1 [TestThread] Thread loop at 2 [TestThread] Thread loop at 3 [TestThread] Thread loop at 4 [TestThread] Thread end. main end!
注意:join方法必须在线程start方法调用之后调用才有意义。这个也很容易理解:如果一个线程都没有start,那它也就无法同步了。
sleep()
:暂停线程Thread.sleep()被用来暂停当前线程的执行,会通知线程调度器把当前线程在指定的时间周期内置为wait状态。当wait时间结束,线程状态重新变为Runnable并等待CPU的再次调度执行。所以线程sleep的实际时间取决于线程调度器,而这是由操作系统来完成的。
sleep()一共两个重载函数:
java.lang.Thread sleep(long millis) 暂停当前线程的执行,暂停时间由方法参数指定,单位为毫秒。注意参数不能为负数,否则程序将会抛出IllegalArgumentException。 java.lang.Thread sleep(long millis, int nanos) 暂停当前线程的执行,暂停时间为millis毫秒数加上nanos纳秒数。纳秒允许的取值范围为0~999999.
测试用例如下:
1 public class SleepDemo implements Runnable { 2 @Override 3 public void run() { 4 System.out.println("i am sleep for a while!"); 5 try { 6 Date currentTime = new Date(); 7 long startTime = currentTime.getTime(); 8 Thread.sleep(4000); 9 currentTime = new Date(); 10 long endTime = currentTime.getTime(); 11 System.out.println("休眠时间为:" + (endTime - startTime) + "ms"); 12 } catch (InterruptedException e) { 13 e.printStackTrace(); 14 } 15 } 16 17 public static void main(String[] args) { 18 SleepDemo runnable = new SleepDemo(); 19 Thread thread = new Thread(runnable); 20 thread.start(); 21 } 22 }
结果为:
>>> i am sleep for a while! 休眠时间为:4002ms
总结:
1. 它只用于暂停当前线程的执行。
2. 线程被唤醒(wake up)并开始执行的实际时间取决于操作系统的CPU时间片长度及调度策略。对于相对空闲的系统来说,sleep的实际时间与指定的sleep时间相近,但对于操作繁忙的系统,这个时间将会显得略长一些。
3. 线程在sleep过程中不会释放它已经获得的任意的monitor和lock等资源。
4. 其他的任意线程都能中断当前sleep的线程,并会抛出InterruptedException。
interrupt()的作用是中断本线程。
1. 本线程中断自己是被允许的;其它线程调用本线程的interrupt()方法时,会通过checkAccess()检查权限。这有可能抛出SecurityException异常。
2. 如果本线程是处于阻塞状态:调用线程的wait(), wait(long)或wait(long, int)会让它进入等待(阻塞)状态,或者调用线程的join(), join(long), join(long, int), sleep(long), sleep(long, int)也会让它进入阻塞状态。若线程在阻塞状态时,调用了它的interrupt()方法,那么它的“中断状态”会被清除并且会收到一个InterruptedException异常。例如,线程通过wait()进入阻塞状态,此时通过interrupt()中断该线程;调用interrupt()会立即将线程的中断标记设为“true”,但是由于线程处于阻塞状态,所以该“中断标记”会立即被清除为“false”,同时,会产生一个InterruptedException的异常。
3. 如果线程被阻塞在一个Selector选择器中,那么通过interrupt()中断它时;线程的中断标记会被设置为true,并且它会立即从选择操作中返回。
4. 如果不属于前面所说的情况,那么通过interrupt()中断线程时,它的中断标记会被设置为“true”。
5. 中断一个“已终止的线程”不会产生任何操作。
例如:
1 class InterruptDemo extends Thread { 2 @Override 3 public void run() { 4 while (!Thread.currentThread().isInterrupted()) { 5 System.out.println("Thread running..."); 6 try { 7 /* 8 * 如果线程阻塞,将不会去检查中断信号量stop变量,所以thread.interrupt() 9 * 会使阻塞线程从阻塞的地方抛出异常,让阻塞线程从阻塞状态逃离出来,并 10 * 进行异常块进行 相应的处理 11 */ 12 // 线程阻塞,如果线程收到中断操作信号将抛出异常 13 Thread.sleep(1000); 14 } catch (InterruptedException e) { 15 System.out.println("Thread interrupted..."); 16 /* 17 * 如果线程在调用 Object.wait()方法,或者该类的 join() 、sleep()方法 18 * 过程中受阻,则其中断状态将被清除 19 */ 20 System.out.println(this.isInterrupted()); 21 // false 22 23 //中不中断由自己决定,如果需要真正中断线程,则需要重新设置中断位,如果 24 //不需要,则不用调用 25 Thread.currentThread().interrupt(); 26 } 27 } 28 System.out.println("Thread exiting under request..."); 29 } 30 31 public static void main(String args[]) throws Exception { 32 InterruptDemo thread = new InterruptDemo(); 33 System.out.println("Starting thread..."); 34 thread.start(); 35 Thread.sleep(3000); 36 System.out.println("Asking thread to stop..."); 37 thread.interrupt();// 等中断信号量设置后再调用 38 Thread.sleep(3000); 39 System.out.println("Stopping application..."); 40 } 41 }
总结
1. Thread.interrupt()方法不会中断一个正在运行的线程。这一方法实际上完成的是,设置线程的中断标示位,在线程受到阻塞的地方(如调用sleep、wait、join等地方)抛出一个异常InterruptedException,并且中断状态也将被清除,这样线程就得以退出阻塞的状态。
2. 一般来说,阻塞函数,如:Thread.sleep、Thread.join、Object.wait等在检查到线程的中断状态时,会抛出InterruptedException,同时会清除线程的中断状态
在java中,每一个对象有且仅有一个同步锁。这也意味着,同步锁是依赖于对象而存在。当我们调用某对象的synchronized方法时,就获取了该对象的同步锁。例如synchronized(obj)就获取了“obj这个对象”的同步锁。
原理
当我们调用某对象的synchronized方法时,就获取了该对象的同步锁。不同线程对同步锁的访问是互斥的,也就是说,某时间点,对象的同步锁只能被一个线程获取到!通过同步锁,我们就能在多线程中,实现对“对象/方法”的互斥访问。 例如,现在有两个线程A和线程B,它们都会访问“对象obj的同步锁”。假设,在某一时刻,线程A获取到“obj的同步锁”并在执行一些操作;而此时,线程B也企图获取“obj的同步锁” —— 线程B会获取失败,它必须等待,直到线程A释放了“该对象的同步锁”之后线程B才能获取到“obj的同步锁”从而才可以运行。
用法
修饰一个普通方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象。
1 class SynchronizedTest1 { 2 public synchronized void method1(){ 3 System.out.println("Method 1 start"); 4 try { 5 System.out.println("Method 1 execute"); 6 Thread.sleep(3000); 7 } catch (InterruptedException e) { 8 e.printStackTrace(); 9 } 10 System.out.println("Method 1 end"); 11 } 12 13 public synchronized void method2(){ 14 System.out.println("Method 2 start"); 15 try { 16 System.out.println("Method 2 execute"); 17 Thread.sleep(1000); 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } 21 System.out.println("Method 2 end"); 22 } 23 } 24 25 public class SynchronizedDemo1{ 26 static void testDemo1(){ 27 SynchronizedTest1 test = new SynchronizedTest1(); 28 new Thread(new Runnable() { 29 @Override 30 public void run() { 31 test.method1(); 32 } 33 }).start(); 34 35 new Thread(() -> test.method2()).start(); 36 } 37 38 public static void main(String[] args) { 39 testDemo1(); 40 } 41 }
线程2需要等待线程1的method1执行完成才能开始执行method2方法。执行结果如下:
>>> Method 1 start Method 1 execute Method 1 end Method 2 start Method 2 execute Method 2 end
修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象。
1 class SynchronizedTest2 { 2 public static synchronized void method1(){ 3 System.out.println("Method 1 start"); 4 try { 5 System.out.println("Method 1 execute"); 6 Thread.sleep(3000); 7 } catch (InterruptedException e) { 8 e.printStackTrace(); 9 } 10 System.out.println("Method 1 end"); 11 } 12 13 public static synchronized void method2(){ 14 System.out.println("Method 2 start"); 15 try { 16 System.out.println("Method 2 execute"); 17 Thread.sleep(1000); 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } 21 System.out.println("Method 2 end"); 22 } 23 } 24 25 public class SynchronizedDemo1 { 26 public static void main(String[] args) { 27 testDemo2(); 28 } 29 30 static void testDemo2() { 31 final SynchronizedTest2 test = new SynchronizedTest2(); 32 final SynchronizedTest2 test2 = new SynchronizedTest2(); 33 new Thread(new Runnable() { 34 @Override 35 public void run() { 36 //不应该通过类实例访问静态成员 com.thread.SynchronizedTest2.method1() 应该通过类名直接访问,这里只是为了演示 37 test.method1(); 38 } 39 }).start(); 40 41 new Thread(new Runnable() { 42 @Override 43 public void run() { 44 test2.method2(); 45 } 46 }).start(); 47 } 48 }
对静态方法的同步本质上是对类的同步(静态方法本质上是属于类的方法,而不是对象上的方法),所以即使test和test2属于不同的对象,但是它们都属于SynchronizedTest类的实例,所以也只能顺序的执行method1和method2,不能并发执行。执行结果如下:
>>> Method 1 start Method 1 execute Method 1 end Method 2 start Method 2 execute Method 2 end
修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象。
1 class SynchronizedTest3 { 2 public void method1(){ 3 System.out.println("Method 1 start"); 4 try { 5 synchronized (this) { 6 System.out.println("Method 1 execute"); 7 Thread.sleep(3000); 8 } 9 } catch (InterruptedException e) { 10 e.printStackTrace(); 11 } 12 System.out.println("Method 1 end"); 13 } 14 15 public void method2(){ 16 System.out.println("Method 2 start"); 17 try { 18 synchronized (this) { 19 System.out.println("Method 2 execute"); 20 Thread.sleep(1000); 21 } 22 } catch (InterruptedException e) { 23 e.printStackTrace(); 24 } 25 System.out.println("Method 2 end"); 26 } 27 28 } 29 30 public class SynchronizedDemo1 { 31 public static void main(String[] args) { 32 testDemo3(); 33 } 34 35 static void testDemo3() { 36 final SynchronizedTest3 test = new SynchronizedTest3(); 37 38 new Thread(new Runnable() { 39 @Override 40 public void run() { 41 test.method1(); 42 } 43 }).start(); 44 45 new Thread(new Runnable() { 46 @Override 47 public void run() { 48 test.method2(); 49 } 50 }).start(); 51 } 52 53 }
虽然线程1和线程2都进入了对应的方法开始执行,但是线程2在进入同步块之前,需要等待线程1中同步块执行完成。执行结果如下:
>>> Method 1 start Method 1 execute Method 2 start Method 1 end Method 2 execute Method 2 end
修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
1 class SyncThread implements Runnable { 2 private static int count; 3 4 public SyncThread() { 5 count = 0; 6 } 7 8 public static void method() { 9 synchronized(SyncThread.class) { 10 for (int i = 0; i < 5; i ++) { 11 try { 12 System.out.println(Thread.currentThread().getName() + ":" + (count++)); 13 Thread.sleep(100); 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 } 17 } 18 } 19 } 20 21 @Override 22 public synchronized void run() { 23 method(); 24 } 25 } 26 27 public class SynchronizedDemo1 { 28 public static void main(String[] args) { 29 testSyncThread(); 30 } 31 32 static void testSyncThread() { 33 SyncThread t = new SyncThread(); 34 new Thread(t).start(); 35 new Thread(t).start(); 36 } 37 38 }
synchronized作用于一个类T时,是给这个类T加锁,T的所有对象用的是同一把锁。顺序执行:
>>> Thread-0:0 Thread-0:1 Thread-0:2 Thread-0:3 Thread-0:4 Thread-1:5 Thread-1:6 Thread-1:7 Thread-1:8 Thread-1:9
基本规则
1. 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
2. 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程仍然可以访问“该对象”的非同步代码块。
3. 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的其他的“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
总结
1、当多个线程同时执行synchronized(x){}同步代码块时呈同步效果。当其他线程执行x对象中的synchronized同步方法时呈同步效果。当其他线程执行x对象方法中的synchronized(this)代码块时也呈同步效果。
2. 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
3. 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
4. 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
Thread yield()方法的作用是暂停当前线程,以便其他线程有机会执行,不过不能指定暂停的时间,并且也不能保证当前线程马上停止。yield方法只是将Running状态转变为Runnable状态。
1 class ThreadA extends Thread{ 2 public ThreadA(String name){ 3 super(name); 4 } 5 @Override 6 public synchronized void run(){ 7 for(int i=0; i <10; i++){ 8 System.out.printf("%s [%d]:%d\n", this.getName(), this.getPriority(), i); 9 // i整除4时,调用yield 10 if (i%4 == 0) { 11 Thread.yield(); 12 } 13 } 14 } 15 } 16 17 public class YieldDemo{ 18 public static void main(String[] args){ 19 ThreadA t1 = new ThreadA("t1"); 20 ThreadA t2 = new ThreadA("t2"); 21 t1.start(); 22 t2.start(); 23 } 24 }
执行结果:
>>> t1 [5]:0 t2 [5]:0 t2 [5]:1 t2 [5]:2 t2 [5]:3 t2 [5]:4 t2 [5]:5 t2 [5]:6 t2 [5]:7 t2 [5]:8 t2 [5]:9 t1 [5]:1 t1 [5]:2 t1 [5]:3 t1 [5]:4 t1 [5]:5 t1 [5]:6 t1 [5]:7 t1 [5]:8 t1 [5]:9
“线程t1”在能被4整数的时候,并没有切换到“线程t2”。这表明,yield()虽然可以让线程由“运行状态”进入到“就绪状态”;但是,它不一定会让其它线程获取CPU执行权(即,其它线程进入到“运行状态”),即使这个“其它线程”与当前调用yield()的线程具有相同的优先级。
总结
1. 使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。cpu会从众多的可执行态里选择,也就是说,当前也就是刚刚的那个线程还是有可能会被再次执行到的,并不是说一定会执行其他线程而该线程在下一次中不会执行到了。
2. 用了yield方法后,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)
3. 通过yield方法来实现两个线程的交替执行。不过请注意:这种交替并不一定能得到保证。
4. yield()只是使当前线程重新回到可执行状态,所有执行yield()的线程有可能在进入到可执行状态后马上又被执行,所以yield()方法只能使同优先级的线程有执行的机会
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。sleep用于线程控制,而wait用于线程间的通信。
1. sleep()使当前线程进入停滞状态(阻塞当前线程),让出CUP的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;
2. sleep()是Thread类的Static(静态)的方法;因此他不能改变对象的机锁,所以当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,但是对象的机锁并木有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。
3. 在sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。
1. wait()方法是Object类里的方法;当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long timeout)超时时间到后还需要返还对象锁);其他线程可以访问;
2. wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。
3. wiat()必须放在synchronized block中,否则会在program runtime时扔出”java.lang.IllegalMonitorStateException“异常。
1. sleep是Thread类的方法,是线程用来 控制自身流程的,比如有一个要报时的线程,每一秒中打印出一个时间,那么我就需要在print方法前面加上一个sleep让自己每隔一秒执行一次。就像个闹钟一样。
2. wait是Object类的方法,用来线程间的通信,这个方法会使当前拥有该对象锁的进程等待知道其他线程调用notify方法时再醒来,不过你也可以给他指定一个时间,自动醒来。这个方法主要是用走不同线程之间的调度的。
1. 关于锁的释放 ,在这里假设大家已经知道了锁的概念及其意义。调用sleep方法不会释放锁(自己的感觉是sleep方法本来就是和锁没有关系的,因为他是一个线程用于管理自己的方法,不涉及线程通信)
2. 调用wait方法会释放当前线程的锁(其实线程间的通信是靠对象来管理的,所有操作一个对象的线程是这个对象通过自己的wait方法来管理的,就好像这个对象是电视机,三个人是三个线程,那么电视机的遥控器就是这个锁,假如现在A拿着遥控器,电视机调用wait方法,那么A就交出自己的遥控器,由jVM虚拟机调度,遥控器该交给谁。)
由于wait函数的特殊意义,所以他是应该放在同步语句块中的,这样才有意义。
1 public class TestWaitAndSleep { 2 public static void main(String[] args) { 3 new Thread(new Thread1()).start(); 4 try { 5 Thread.sleep(5000); 6 } catch (Exception e) { 7 e.printStackTrace(); 8 } 9 new Thread(new Thread2()).start(); 10 } 11 12 private static class Thread1 implements Runnable{ 13 @Override 14 public void run(){ 15 synchronized (TestWaitAndSleep.class) { 16 System.out.println("enter thread1..."); 17 System.out.println("thread1 is waiting..."); 18 try { 19 //调用wait()方法,线程会放弃对象锁,进入等待此对象的等待锁定池 20 TestWaitAndSleep.class.wait(); 21 } catch (Exception e) { 22 e.printStackTrace(); 23 } 24 System.out.println("thread1 is going on ...."); 25 System.out.println("thread1 is over!!!"); 26 } 27 } 28 } 29 30 private static class Thread2 implements Runnable{ 31 @Override 32 public void run(){ 33 synchronized (TestWaitAndSleep.class) { 34 System.out.println("enter thread2...."); 35 System.out.println("thread2 is sleep...."); 36 //只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。 37 TestWaitAndSleep.class.notify(); 38 //================== 39 //区别 40 //如果我们把代码:TestD.class.notify();给注释掉,即TestD.class调用了wait()方法,但是没有调用notify() 41 //方法,则线程永远处于挂起状态。 42 try { 43 //sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程, 44 //但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。 45 //在调用sleep()方法的过程中,线程不会释放对象锁。 46 Thread.sleep(5000); 47 } catch (Exception e) { 48 e.printStackTrace(); 49 } 50 System.out.println("thread2 is going on...."); 51 System.out.println("thread2 is over!!!"); 52 } 53 } 54 } 55 }
运行结果:
>>> enter thread1... thread1 is waiting... enter thread2.... thread2 is sleep.... thread2 is going on.... thread2 is over!!! thread1 is going on .... thread1 is over!!!
如果注释掉代码:TestD.class.notify();
运行结果:
>>> enter thread1... thread1 is waiting... enter thread2.... thread2 is sleep.... thread2 is going on.... thread2 is over!!!
且程序一直处于挂起状态。
总结
1. sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。
2. 调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备