Java线程休眠的四种方式:sleep()、wait()、await()、park()、join()

1,083 阅读7分钟

Thrad.sleep()

  • Thrad.sleep()并不会在休眠的时候释放锁
  • Thrad.sleep()有两个重载方法,分别为sleep(long millis)和sleep(long millis, int nanos)
@Test
public void test01() throws InterruptedException {
    Object lock = new Object();
    Thread t1 = new Thread(() -> {
        try {
            synchronized (lock) {
                System.out.println(SDF.format(new Date()) + "子线程获取到锁并休眠10s");
                Thread.sleep(10000);
            }
            System.out.println(SDF.format(new Date()) + "子线休眠完毕程释放锁");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    });
    t1.start();
    Thread.sleep(100);
    System.out.println(SDF.format(new Date()) + "主线程尝试获取锁");
    synchronized (lock) {
        System.out.println(SDF.format(new Date()) + "主线程获取到锁");
    }
}
  • 运行结果
2023/04/10 22:23:37子线程获取到锁并休眠10s
2023/04/10 22:23:37主线程尝试获取锁
2023/04/10 22:23:47主线程获取到锁
2023/04/10 22:23:47子线休眠完毕程释放锁

根据运行结果可以知道主线程一直在等待子线程休眠完成释放锁

  • 给子线程设置中断状态可以释放锁并抛出interruptedException
@Test
public void test01() throws InterruptedException {
    Object lock = new Object();
    Thread t1 = new Thread(() -> {
        try {
            synchronized (lock) {
                System.out.println(SDF.format(new Date()) + "子线程获取到锁并休眠10s");
                Thread.sleep(10000);
            }
            System.out.println(SDF.format(new Date()) + "子线休眠完毕程释放锁");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    });
    t1.start();
    Thread.sleep(100);
    t1.interrupt(); // 给子线程设置中断状态
    System.out.println(SDF.format(new Date()) + "主线程尝试获取锁");
    synchronized (lock) {
        System.out.println(SDF.format(new Date()) + "主线程获取到锁");
    }
}
  • 运行结果
2023/04/10 22:25:41子线程获取到锁并休眠10s
2023/04/10 22:25:41主线程尝试获取锁
2023/04/10 22:25:41主线程获取到锁
Exception in thread "Thread-0" java.lang.RuntimeException: java.lang.InterruptedException: sleep interrupted
    at ThreadTest.lambda$test01$0(ThreadTest.java:20)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at ThreadTest.lambda$test01$0(ThreadTest.java:16)
    ... 1 more

Object.wait()

  • 该方法属于Object定义的方法
  • Object.wait()休眠的时候会释放锁
  • Object.wait()有三个重载方法,分别为wait()、wait(long timeout)和wait(long timeout, int nanos)
@Test
public void test02() throws InterruptedException {
    Object lock = new Object();
    Thread t1 = new Thread(() -> {
        try {
            synchronized (lock) {
                System.out.println(SDF.format(new Date()) + "子线程获取到锁并休眠10s");
                lock.wait(5000);
            }
            System.out.println(SDF.format(new Date()) + "子线休眠完毕程释放锁");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    });
    t1.start();
    Thread.sleep(100);
    System.out.println(SDF.format(new Date()) + "主线程尝试获取锁");
    synchronized (lock) {
        System.out.println(SDF.format(new Date()) + "主线程获取到锁");
        Thread.sleep(6000);
        System.out.println(SDF.format(new Date()) + "主线程释放锁");
    }
    Thread.sleep(5000);
}
  • 运行结果
2023/04/11 20:41:52子线程获取到锁并休眠10s
2023/04/11 20:41:52主线程尝试获取锁
2023/04/11 20:41:52主线程获取到锁
2023/04/11 20:41:58主线程释放锁
2023/04/11 20:41:58子线休眠完毕程释放锁

子线程lock.wait(5000)进行休眠后,主线程立即获取到锁,随即主线程进行Thread.sleep(6000)休眠但是不释放锁,子线程一直在等待主线程将锁释放。待主线程释放锁后,子线程拿到锁后执行完毕

  • 同样给子线程设置中断状态可以释放锁并抛出interruptedException
@Test
public void test02() throws InterruptedException {
    Object lock = new Object();
    Thread t1 = new Thread(() -> {
        try {
            synchronized (lock) {
                System.out.println(SDF.format(new Date()) + "子线程获取到锁并休眠10s");
                lock.wait();
            }
            System.out.println(SDF.format(new Date()) + "子线休眠完毕程释放锁");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    });
    t1.start();
    Thread.sleep(100);
    t1.interrupt(); // 给子线程设置中断状态
    System.out.println(SDF.format(new Date()) + "主线程尝试获取锁");
    synchronized (lock) {
        System.out.println(SDF.format(new Date()) + "主线程获取到锁");
    }
}
  • 运行结果
2023/04/11 20:51:07子线程获取到锁并休眠10s
2023/04/11 20:51:07主线程尝试获取锁
2023/04/11 20:51:07主线程获取到锁
Exception in thread "Thread-0" java.lang.RuntimeException: java.lang.InterruptedException
	at ThreadTest.lambda$test02$1(ThreadTest.java:43)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at ThreadTest.lambda$test02$1(ThreadTest.java:39)
	... 1 more

使用Object.wait()进行线程休眠时,可通过Object.notify()和Object.notifyAll()进行线程唤醒

notify()每次会唤醒第一个线程,接下来计算唤醒次数,唤醒接下来的n个等待线程,并倒序执行。例如10个线程正在休眠,notify()for循环执行三次,则唤醒的三个线程分别是Thread0、Thread2、Thread1

notifyAll()则会倒序唤醒每个线程

condition.await()

  • 与Object.wait类似
  • 对应Object.wait()方法: await()、awaitNanos(long nanosTimeout)、await(long time, TimeUnit unit)
  • 对应Object.notify()、notifyAll()方法:signal()、signalAll()
  • 同时await休眠时会释放锁
  • 使用condition.awaitUninterruptibly()休眠时,可以忽略中断报错
@Test
public void test03() throws InterruptedException {
    ReentrantLock lock = new ReentrantLock();
    int queueMaxSize = 10;
    LinkedList<Integer> queue = new LinkedList();
    Condition consumerCondition = lock.newCondition();
    Condition producerCondition = lock.newCondition();

    // 生产者
    Thread producer = new Thread(() -> {
        try {
            boolean flag = Boolean.TRUE;
            lock.lock();
            while (flag) {
                while (queue.size() == queueMaxSize) {
                    System.out.println("队列已满,暂停生产");
                    producerCondition.await(); // 调用condition的await()和signal()方法,都必须在lock保护之内
                }
                queue.offer(1);
                consumerCondition.signal();
                System.out.println("生产者生产一个元素,当前队列size=" + queue.size());
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    });

    // 消费者
    Thread consumer = new Thread(() -> {
        try {
            boolean flag = Boolean.TRUE;
            lock.lock();
            while (flag) {
                while (queue.size() == 0) {
                    System.out.println("队列已空,暂停消费");
                    consumerCondition.await(); // 调用condition的await()和signal()方法,都必须在lock保护之内
                }
                queue.poll();
                producerCondition.signal();
                System.out.println("消费者消费一个元素,当前队列size=" + queue.size());
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    });

    producer.start();
    consumer.start();

    Thread.sleep(10000);
}
  • 运行结果
队列已空,暂停消费
生产者生产一个元素,当前队列size=1
生产者生产一个元素,当前队列size=2
生产者生产一个元素,当前队列size=3
生产者生产一个元素,当前队列size=4
生产者生产一个元素,当前队列size=5
生产者生产一个元素,当前队列size=6
生产者生产一个元素,当前队列size=7
生产者生产一个元素,当前队列size=8
生产者生产一个元素,当前队列size=9
生产者生产一个元素,当前队列size=10
队列已满,暂停生产
消费者消费一个元素,当前队列size=9
消费者消费一个元素,当前队列size=8
消费者消费一个元素,当前队列size=7
消费者消费一个元素,当前队列size=6
消费者消费一个元素,当前队列size=5
消费者消费一个元素,当前队列size=4
消费者消费一个元素,当前队列size=3
消费者消费一个元素,当前队列size=2
消费者消费一个元素,当前队列size=1
消费者消费一个元素,当前队列size=0

使用condition.await()配合condition.signal()可以很容易实现一个生产者、消费者模式

LockSupport.park()

  • LockSupport.park() 的实现原理是通过二元信号量做的阻塞。0 是阻塞,1是通行。unpark()方法会将信号量变为 1,不论执行多少次unpark(这里指凭证没有被消费),也只能变成1。
  • t1线程没有启动时,其他线程的unpark(),t1 接收不到
@Test
public void test05() throws InterruptedException {
    Thread t1 = new Thread(() -> {
        System.out.println("子线程开始休眠,等待被唤醒");
        LockSupport.park();
        System.out.println("子线程被唤醒,继续休眠");
        LockSupport.park();
        System.out.println("子线程响应中断,但是没有异常,执行完毕");
    });

    LockSupport.unpark(t1);
    System.out.println("主线程唤醒子线程1");
    Thread.sleep(100);
    t1.start();
    Thread.sleep(100);
    System.out.println("主线程唤醒子线程2");
    LockSupport.unpark(t1);
    Thread.sleep(1000);
    System.out.println("主线程对子线程进行中断处理");
    t1.interrupt();

    Thread.sleep(10000); // 确保子线程执行完毕
}
  • 运行结果
主线程唤醒子线程1
子线程开始休眠,等待被唤醒
主线程唤醒子线程2
子线程被唤醒,继续休眠
主线程对子线程进行中断处理
子线程响应中断,但是没有异常,执行完毕

子线程在启动之前执行unpark(t1)是不起效果的

LockSupport.park()后有对应的LockSupport.unpark(t1)即可被唤醒

LockSupport.park()可以响应中断被唤醒,但是不会报异常错误

Thread.join()

  • Thread.join()有三个重载方法,分别为join()、join(long timeout)和join(long millis, int nanos)
  • 我们经常会碰到主线程中起了一个子线程,但子线程还未执行完毕主线程就已经结束了。此时需要让线程按照自己指定的顺序执行的时候,就可以利用这个方法
  • Thread.join()方法表示调用此方法的线程被阻塞,仅当该方法完成以后,才能继续运行
@Test
public void test06() throws InterruptedException {
    Thread t1 = new Thread(() -> {
        try {
            System.out.println("t1线程开始休眠5s");
            Thread.sleep(5000);
            System.out.println("t1线程休眠结束");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    });
    Thread t2 = new Thread(() -> {
        try {
            System.out.println("t2线程等待t1线程");
            t1.join();
            System.out.println("t2线程开始休眠5s");
            Thread.sleep(5000);
            System.out.println("t2线程休眠结束");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    });

    t1.start();
    Thread.sleep(200);
    t2.start();
    Thread.sleep(200);
    System.out.println("主线程等待t2线程");
    t2.join();
    System.out.println("主线程执行完毕");
}
  • 运行结果
t1线程开始休眠5s
t2线程等待t1线程
主线程等待t2线程
t1线程休眠结束
t2线程开始休眠5s
t2线程休眠结束
主线程执行完毕

主线程一直在等待t2线程执行完毕,而t2线程又在等待t1线程,因此可以看到线程按照预想的顺序进行执行