java中的wait和sleep

线程的状态

Java中线程的状态分为 6 种:

  1. 初始(NEW):新建了一个线程对象,但是还没调用start() 方法
  2. 运行(RUNNABLE):Java 线程中将就绪(ready)和运行中(running)两种状态统称为“运行”。线程对象在创建之后,其他线程(比如 main 线程)调用了该对象的 start() 方法,该状态的线程位于可变线程池中,等待被线程调度选中,获取CPU 的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得 CPU时间片后变为运行状态(running)。
  3. 阻塞(BLOCKED):表示线程阻塞与锁。
  4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定的动作(通知或者中断)。
  5. 超时等待(TIMED_WAITING):该状态不同于 WAITING,它可以在指定的时间后自行返回。
  6. 终止(TERMINATED):表示该线程已经执行完毕。

wait

使用场景

获取到某个线程之后,发现当前并不需要使用该线程,则可以调用wait() 方法,使其进入等待过程。

等到某个时刻,满足条件之后,则可以使用notify() 或者 notifyAll() 方法来唤醒这个线程。

使用条件

对于已经获取到锁的线程才可以调用锁的wait(),notify()方法,否则会抛出IllegalMonitorStateException 异常。

比如下面的代码,A获得了锁之后,主动调用 wait() 方法释放锁和CPU资源,然后就进入了阻塞状态。

主线程在没有获得锁的情况之下,直接调用notify() 方法会抛出异常。

public class WaitTest {

    public static void main(String[] args) {
        Object lock = new Object();
        Thread threadA = new Thread(() -> {
            synchronized (lock) {
                System.out.println("获取到锁");
                try {
                    System.out.println("休眠一会");
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("调用wait");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("被唤醒");
            }
        }, "A");
        threadA.start();

        lock.notify();
    }
}

输出结果:

java中的wait和sleep_第1张图片

现在我们创建一个线程B,抢占锁后,再调用 notify() 方法

public class WaitTest {

    public static void main(String[] args) {
        Object lock = new Object();
        Thread threadA = new Thread(() -> {
            synchronized (lock) {
                System.out.println("A 获取到锁");
                try {
                    System.out.println("休眠一会");
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("调用wait");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("A 被唤醒");
            }
        }, "A");
        threadA.start();

        Thread threadB = new Thread(() -> {
            synchronized (lock) {
                System.out.println("B 获取了锁");

                System.out.println("B 唤醒 A");
                lock.notify();
            }
        },"B");
        threadB.start();
    }
}

线程状态变化

首先我们需要知道,线程正常运行时的状态是 RUNNABLE,调用 wait() 方法后,变为 WAITING 状态。

举例说明:

public class WaitTest {

    public static void main(String[] args) {
        Object lock = new Object();
        Thread threadA = new Thread(() -> {
            synchronized (lock) {
                System.out.println("A 获取到锁");
                try {
                    System.out.println("休眠一会");
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("调用wait");
                try {
                    System.out.println("调用wait前的线程状态:"+Thread.currentThread().getState());
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("A 被唤醒");
            }
        }, "A");
        threadA.start();

        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("调用wait后的线程状态:"+threadA.getState());

    }
}

输出结果:

java中的wait和sleep_第2张图片

提问:主动 wait 的线程,被唤醒之后,它的状态一定是从 WAITING 变成 RUNNABLE 吗?

我们再做一个测试:A线程wait,2秒后,启动B线程,B线程去唤醒A线程。记录下唤醒前后的状态。

public class WaitTest {

    public static void main(String[] args) {
        Object lock = new Object();
        Thread threadA = new Thread(() -> {
            synchronized (lock) {
                System.out.println("A 获取到锁");
                try {
                    System.out.println("休眠一会");
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("调用wait");
                try {
                    System.out.println("调用wait前的线程状态:"+Thread.currentThread().getState());
                    lock.wait();
                    System.out.println("调用notify后的线程状态:"+Thread.currentThread().getState());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("A 被唤醒");
            }
        }, "A");
        threadA.start();

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Thread threadB = new Thread(() -> {
           synchronized (lock) {
               System.out.println("B 获取到锁");
               System.out.println("叫醒A前,A的状态:"+threadA.getState());
               System.out.println("叫醒A");
               lock.notify();
           }
        },"B");
        threadB.start();
    }
}

输出结果:

java中的wait和sleep_第3张图片

上面的例子与我们的猜测是一致的。

但是,这段代码并不严谨,上述代码的 B线程在调用了 notify() 方法之后,立即释放了锁,但假如 B线程调用 notify() 后,还有很多任务没有完成,不立即释放锁,A线程的状态又会有什么变化?

修改后的代码:

public class WaitTest {

    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        Thread threadA = new Thread(() -> {
            synchronized (lock) {
                System.out.println("A 获取到锁");
                try {
                    System.out.println("休眠一会");
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("调用wait");
                try {
                    System.out.println("调用wait前的线程状态:"+Thread.currentThread().getState());
                    lock.wait();
                    System.out.println("调用notify后的线程状态:"+Thread.currentThread().getState());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("A 被唤醒");
            }
        }, "A");
        threadA.start();

        TimeUnit.SECONDS.sleep(2);

        Thread threadB = new Thread(() -> {
           synchronized (lock) {
               System.out.println("B 获取到锁");
               System.out.println("叫醒A前,A的状态:"+threadA.getState());
               System.out.println("叫醒A");
               lock.notify();
               System.out.println("发现还有很多事要做,先不释放锁");
               System.out.println("B 在做其他事的时候,A 的状态:"+threadA.getState());
               try {
                   TimeUnit.SECONDS.sleep(2);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               System.out.println("B 的事情都做完了");
           }
        },"B");
        threadB.start();
    }
}

输出结果:

java中的wait和sleep_第4张图片

通过观察我们发现,A 的状态并没有立刻变成 RUNNALBE,而是 BLOCKED,等到 B线程任务处理完释放锁之后,A 的状态才变成 RUNNALBE。

也就是说,B调用完 notify() 方法后,并不会立刻把线程的控制权交出去,A 被唤醒后,也不会立即将状态变成 RUNNABLE,而是先变成 BLOCKED,然后参与锁竞争,竞争成功重新获得锁之后,再向下执行。

Java中,每一个对象都有一个对应的 Monitor 锁,Monitor维护着 EntrySet 和 WaitSet。线程阻塞时,对象会被放入到 EntrySet 中,对应的状态是 BLOCKED。线程调用wait方法后,会被加入到WaitSet中,对应的状态是WAITING、TIMED_WAITIING。

上面我们分析过,线程“被唤醒”和“获得锁”是两个过程,被唤醒的线程需要重新参与锁竞争。

这么理解的话,那么线程应该是从 WaitSet 中苏醒后,又被加入到 EntrySet 队列。

有限等待

wait() 方法可以传入一个时间参数,是一种自动唤醒机制:在指定的时间内,如果没有其他线程唤醒自己,则主动唤醒自己。传 0 或者不传,则表示永久等待。

public class WaitTest {

    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        Thread threadA = new Thread(() -> {
            synchronized (lock) {
                System.out.println("A 获取到锁");
                try {
                    System.out.println("休眠一会");
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("调用wait");
                try {
                    System.out.println("调用wait前的线程状态:"+Thread.currentThread().getState());
                    System.out.println("当前时间:"+System.currentTimeMillis());
                    lock.wait(1000);
                    System.out.println("调用notify后的线程状态:"+Thread.currentThread().getState());
                    System.out.println("当前时间:"+System.currentTimeMillis());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("A 被唤醒");
            }
        }, "A");
        threadA.start();

    }
}

输出结果:

java中的wait和sleep_第5张图片

这只是一种情况,如果 A 被唤醒后,继续参加锁的竞争,假设 A 在wait(t) 的过程中,有其他线程抢占了线程,A还能继续执行吗?

以下是代码示例:

public class WaitTest {

    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        Thread threadA = new Thread(() -> {
            synchronized (lock) {
                System.out.println("A 获取到锁");
                try {
                    System.out.println("休眠一会");
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("调用wait");
                try {
                    System.out.println("调用wait前的线程状态:"+Thread.currentThread().getState());
                    System.out.println("当前时间:"+System.currentTimeMillis());
                    lock.wait(100);
                    System.out.println("调用notify后的线程状态:"+Thread.currentThread().getState());
                    System.out.println("当前时间:"+System.currentTimeMillis());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("A 被唤醒");
            }
        }, "A");
        threadA.start();

        Thread threadB = new Thread(() -> {
           synchronized (lock) {
               try {
                   TimeUnit.SECONDS.sleep(2);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               System.out.println("A 的状态:"+threadA.getState());
               while (true) {

               }
           }
        },"B");
        threadB.start();
    }
}

输出结果:

java中的wait和sleep_第6张图片

即使 1000ms 过了,A 也不能继续执行,因为 A 只是被唤醒了,但是并没有成功获得锁,所以会进入 BLOCKED 状态。

sleep

Java中的线程休眠可以使用 Thread 类的静态方法 sleep()实现,该方法可以让当前线程暂停执行一段时间,以等待其他线程完成某些操作,或者为了节省系统资源而暂停线程执行。

代码示例:

public class SleepThreadExample {
    public static void main(String[] args) {
        System.out.println("线程开始执行");
        try {
            Thread.sleep(5000); // 休眠5秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程结束执行");
    }
}

在上面的代码中,我们使用sleep() 方法让线程休眠5 秒,然后再继续执行。如果在休眠期间线程被终端,就会抛出 InterruptedException异常。

需要注意的是,sleep() 方法会暂停当前线程的执行,因此如果在主线程中调用该方法,就会导致整个程序暂停执行,直到指定的时间到达。因此,我们应该避免在主线程中过渡使用sleep()方法,以免影响程序的响应性和用户体验。

sleep和wait的异同点

相同点

  1. 都可以使线程堵塞
  2. 都可以响应中断

不同点

  1. wait、notify方法必须写在同步方法中,是为了防止死锁和永久等待,使线程更安全,而sleep方法不需要有这个限制。
  2. wait方法调用后会释放锁,sleep方法调用后不会释放锁。
  3. sleep方法必须要指定时间参数,wait方法可以指定时间参数,但不是必需的
  4. 两个方法所属类不同,sleep方法属于Thread类;wait属于Object类中,放在Object类中是因为Java中每个类都可以是一把锁。

你可能感兴趣的:(java,开发语言)