等待跟唤醒

:::tips

概述:

  • 等待和唤醒,指的是两个线程或多个线程之间的相互等待或唤醒
  • Java中提供了几个方法可以实现线程的等待和唤醒,这几个方法都来自Object类

常用方法:

void wait() 让当前线程释放锁并进入等待,直到其他线程调用锁的 notify() 或 notifyAll()方法
void notify() 唤醒正在等待对象监视器(锁)的单个线程
void notifyAll() 唤醒正在等待对象监视器(锁)的所有线程

wait:线程等待

  • 在一个同步代码块内,线程调用锁对象的 wait() 方法,让自己释放同步锁,并进入等待状态。只有其他线程调用了锁对象的 notify() 方法,等待的线程才可能解除等待,重新参与锁对象的竞争,线程如果再次得到锁,就可以从wait处继续向下运行

notify:线程唤醒

  • 在一个同步代码块内,线程调用锁对象的 notify() 方法,唤醒正在等待的另一个线程。(被唤醒的线程需要重新抢夺锁,成功之后可以从wait处继续往下执行。)如果有多个线程同时在等待,notify() 方法只会随机唤醒某个线程,如果想唤醒所有等待的线程,可以使用notifyAll() 方法

注意事项

  • 等待和唤醒是多个线程之间的操作
  • wait() ,notify() ,notifyAll() 方法 必须在同步代码块中使用锁对象调用
  • notify() 和 notifyAll() 方法并不释放锁,只是告诉在等待锁的线程可以去参与获得锁的竞争了,但被唤醒的线程不是马上得到锁,因为锁还在别人手里没释放 :::

线程死锁

:::tips

容易死锁条件

  • 有多把锁
  • 有多个线程
  • 有同步代码块嵌套 :::

线程的状态

:::tips

在Thread.State枚举类中,定义了线程的6种状态:

  • 新建状态(NEW)
  • 可运行状态(RUNNABLE)
  • 终止状态(TERMINATED)
  • 阻塞状态(BLOCKED)
  • 无限等待状态(WAITING)
  • 计时等待(TIMED_WAITING)

image.png :::

线程池

:::tips

好处:

  • 资源利用
  • 提高效率

创建线程池

  • Executors类是线程池的工具类,通过Executors工具类可以创建线程池

    常用方法:
  • static ExecutorService newFixedThreadPool(int nThreads) 建一个线程池,参数为池中的线程数

使用线程池

  • ExecutorService代表线程池,该类中提供了 submit 方法用于处理提交的任务
  • 调用 submit(任务) 方法时,线程池会分配池中空闲的线程去执行对应的任务

    常用方法
  • submit(Runnable task) 提交Runnable类型的任务

  • submit(Callable task) 提交Callable类型的任务
  • void shutdown() 关闭连接池

    创建任务的两种方式
  • 实现Runnable接口,重写run方法

  • 实现 Callable<返回值类型> 接口,重写call方法

    Callable方式好处:
  • 有返回值

    • Future f = 线程池.submit(Callable任务);
    • 返回值类型 变量 = f.get()
  • 可以抛异常。

线程池执行原理:

  • 任务通过submit方法提交给线程池
  • 线程池分配线程执行任务,执行结束后线程放回线程池,等待执行下次任务
  • 当线程池中没有空闲线程时,任务进入任务队列中等待,直到有空闲的线程去执行任务 :::
    1. public static void main(String[] args) {
    2. Object obj = new Object();
    3. //开始游戏线程
    4. Thread thread1 = new Thread(new Runnable() {
    5. @Override
    6. public void run() {
    7. System.out.println("准备开黑");
    8. synchronized (obj) {
    9. try {
    10. //释放obj锁,并进入等待
    11. obj.wait();
    12. } catch (InterruptedException e) {
    13. e.printStackTrace();
    14. }
    15. }
    16. System.out.println("游戏开始");
    17. }
    18. });
    19. thread1.start();
    20. //加载游戏线程
    21. Thread thread2 = new Thread(new Runnable() {
    22. @Override
    23. public void run() {
    24. System.out.println("玩家加载");
    25. for (int i = 1; i <= 5; i++) {
    26. System.out.println("玩家" + i + "准备就绪");
    27. try {
    28. Thread.sleep(1000);
    29. } catch (InterruptedException e) {
    30. e.printStackTrace();
    31. }
    32. }
    33. System.out.println("加载完毕");
    34. synchronized (obj) {
    35. //唤醒正在等待的obj锁对象的线程,被唤醒的线程需要重新获取锁,然后往下执行
    36. obj.notify();
    37. }
    38. }
    39. });
    40. thread2.start();
    41. }
    1. /*
    2. 两个线程交替打印奇偶数
    3. 偶数线程
    4. 如果是偶数,打印并自增,通知notify另外一条线程打印
    5. 如果是奇数,进入等待wait
    6. 奇数线程
    7. 如果是奇数,打印并自增,通知notify另外一条线程打印
    8. 如果是偶数,进入等待wait
    9. */
    10. static final Object obj = new Object();
    11. //共享静态变量
    12. static int i = 0;
    13. public static void main(String[] args) {
    14. Thread thread1 = new Thread(new Runnable() {
    15. @Override
    16. public void run() {
    17. Thread thread1 = Thread.currentThread();
    18. thread1.setName("偶数线程");
    19. String name = thread1.getName();
    20. synchronized (obj) {
    21. while (i <= 10) {
    22. if (i % 2 == 0) {
    23. System.out.println(name + ":" + i);
    24. //自增并通知另外一条线程打印
    25. i++;
    26. obj.notify();
    27. } else {
    28. //进入等待
    29. try {
    30. obj.wait();
    31. } catch (InterruptedException e) {
    32. e.printStackTrace();
    33. }
    34. }
    35. }
    36. }
    37. }
    38. });
    39. Thread thread2 = new Thread(new Runnable() {
    40. @Override
    41. public void run() {
    42. Thread thread2 = Thread.currentThread();
    43. thread2.setName("奇数线程");
    44. String name = thread2.getName();
    45. synchronized (obj) {
    46. while (i <= 10) {
    47. if (i % 2 != 0) {
    48. System.out.println(name + ":" + i);
    49. i++;
    50. obj.notify();
    51. } else {
    52. try {
    53. obj.wait();
    54. } catch (InterruptedException e) {
    55. e.printStackTrace();
    56. }
    57. }
    58. }
    59. }
    60. }
    61. });
    62. thread1.start();
    63. thread2.start();
    64. }
    ```java //线程池的线程默认名字为pool-线程池编号-thread-线程编号 public static void main(String[] args) { //获取线程池对象,参数为线程池线程数量 //线程数量设置要看具体业务 ExecutorService pool = Executors.newFixedThreadPool(3); pool.submit(new DemoExecutors()); pool.submit(new DemoExecutors()); pool.submit(new DemoExecutors()); pool.submit(new DemoExecutors()); pool.submit(new DemoExecutors()); pool.submit(new DemoExecutors()); //销毁线程池 实际开发中一般不用 //pool.shutdown(); }

public class DemoExecutors implements Runnable{ @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } Thread thread = Thread.currentThread(); String name = thread.getName(); System.out.println(“[“ + name + “]: 执行了” ); } }

```java
public static void main(String[] args) throws ExecutionException, InterruptedException {
    //创建线程池
    ExecutorService pool = Executors.newFixedThreadPool(2);
    //创建任务
    DemoCallable callable = new DemoCallable();
    //提交任务到线程池,得到线程执行返回值,
    //执行submit得到Future对象,里面封装了方法的返回值
    Future<Integer> submit = pool.submit(callable);
    //调用Future对象的get方法,阻塞等待线程的返回, 需抛出异常
    Integer i = submit.get();

    System.out.println("休眠了:" + i + "s");
}

public class DemoCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("线程执行");

        Random ran = new Random();
        int i = ran.nextInt(3) + 1;
        Thread.sleep(i * 1000);

        System.out.println("休眠结束");

        return i;
    }
}
/*
需求:使用线程池方式创建两个线程任务:分段计算1~20000之间的数字和。
    1.线程1计算1~10000之间的数字和,并返回结果。
    2.线程2计算10001~20000之间的数字和,并返回结果。
    3.提交任务,获取计算结果进行合并,打印最终结果。
*/
public class DemoCallable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(2);
        DemoSum demoSum = new DemoSum();

        Future<Integer> future1 = pool.submit(() -> demoSum.sum(1, 100));

        Future<Integer> future2 = pool.submit(() -> demoSum.sum(100, 201));

        Integer num1 = future1.get();
        Integer num2 = future2.get();

        System.out.println("和为:" + (num1 + num2));
    }
}

class DemoSum {
    public int sum(int a, int b) {
        int num = 0;
        for (int i = a; i < b; i++) {
            num += i;
        }
        return num;
    }
}