java多线程学习 基础篇(三) Thread类的常用方法
线程Thread是一个程序的多个执行路径,执行调度的单位,依托于进程存在。 线程不仅可以共享进程的内存,而且还拥有一个属于自己的内存空间,这段内存空间也叫做线程栈,是在建立线程时由系统分配的,主要用来保存线程内部所使用的数据,如线程执行函数中所定义的变量。
Java中的多线程是一种抢占机制而不是分时机制。抢占机制指的是有多个线程处于可运行状态,但是只允许一个线程在运行,他们通过竞争的方式抢占CPU。
下面介绍一些常用的Thread方法。
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,那它也就无法同步了。
Thread.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。
Thread.interrupt() 中断线程
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,同时会清除线程的中断状态
Thread synchronized同步锁
在java中,每一个对象有且仅有一个同步锁。这也意味着,同步锁是依赖于对象而存在。当我们调用某对象的synchronized方法时,就获取了该对象的同步锁。例如synchronized(obj)就获取了“obj这个对象”的同步锁。
原理
当我们调用某对象的synchronized方法时,就获取了该对象的同步锁。不同线程对同步锁的访问是互斥的,也就是说,某时间点,对象的同步锁只能被一个线程获取到!通过同步锁,我们就能在多线程中,实现对“对象/方法”的互斥访问。 例如,现在有两个线程A和线程B,它们都会访问“对象obj的同步锁”。假设,在某一时刻,线程A获取到“obj的同步锁”并在执行一些操作;而此时,线程B也企图获取“obj的同步锁” —— 线程B会获取失败,它必须等待,直到线程A释放了“该对象的同步锁”之后线程B才能获取到“obj的同步锁”从而才可以运行。
用法
1. 修饰普通方法,对普通方法同步
修饰一个普通方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象。
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
2. 修饰静态方法,静态方法(类)同步
修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象。
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
3. 修饰代码块,代码块同步
修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象。
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
4. 修饰一个类,对类同步
修饰一个类,其作用的范围是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() 线程协作让步
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()方法只能使同优先级的线程有执行的机会
Thread sleep(),wait()区别详解
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。sleep用于线程控制,而wait用于线程间的通信。
sleep()简介
1. sleep()使当前线程进入停滞状态(阻塞当前线程),让出CPU的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;
2. sleep()是Thread类的Static(静态)的方法;因此他不能改变对象的机锁,所以当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,但是对象的机锁并没有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。
3. 在sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。
wait()简介
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()方法后本线程才进入对象锁定池准备