05-线程相关方法

nobility 发布于 2021-05-01 743 次阅读


线程相关方法

线程等待与唤醒机制

注意事项

线程等待和唤醒的方法必须在有synchronized保护的代码块或方法中执行,并且是使用该保护代码块的锁对象进行方法的调用,也就是必须先拥有monitor锁,否则会抛出IllegalMonitorStateException非法monitor锁状态异常;之所以这样设计是因为,若不是在同步代码块中的话,由于线程的随机性可能会产生死锁,比如说两个线程,一个是要唤醒,另一个是要等待,若先等待才能被唤醒,反之就会陷入永久等待,所以该操作就需要安全的进行控制

线程等待和唤醒方法是定义在Object对象上的final native方法,调用等待或唤醒方法的是同一个对象,否则不是同一把锁会无法唤醒,之所以这样设计是因为,这些方法是锁级别的操作,而锁是属于某个对象的(绑定在对象头中),而不是线程中,若将锁定义在线程中则没个线程只能有一把锁,并且也不要使用线程对象的等待方法,这是因为在每个线程执行结束时该线程对象都会都会自动调用notifyAll()方法

线程等待与唤醒

  • wait():调用该方法的线程会处于等待状态,同时释放调用该方法对象的monitor锁
  • notify():随机唤醒一个等待线程
  • notifyAll():唤醒全部等待线程,那个优先级高那个先执行

处于等待状态的线程,只有发生以下情况才会被唤醒

  • 另一个线程调用这个对象的notify()方法,且刚好被唤醒的是该线程
  • 另一个线程调用这个对象的notifyAll()方法
  • 使用了可设置了等待时间的wait()方法,并且已经等待超时(若等待时间设置为0则永久等待)
  • 该线程调用了interrupt()方法
public static void main(String[] args) throws InterruptedException {
  Object lock = new Object();  //用来当锁的对象
  Thread thread = new Thread(() -> {
    System.out.println(Thread.currentThread().getName() + "线程开始执行");
    synchronized (lock) {
      try {
        lock.wait();  //当前线程会进入等待状态,同时释放锁
        //lock.wait(500);  //等待指定时间,超时后自动唤醒,同时获取锁
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.println(Thread.currentThread().getName() + "线程获取到了锁");
    }
  });
  thread.start();
  Thread.sleep(1000);  //为了让thread线程先执行

  synchronized (lock) {
    lock.notify();  //线程唤醒
    //lock.notifyAll();  //全部线程唤醒
  }
  //thread.interrupt();  //线程中断

  System.out.println(Thread.currentThread().getName() + "线程执行了唤醒操作");
}

生产者消费者模式

使用两个线程或进程对数据操作进行解耦(不局限两个),共用一个块数据,一个负责消费数据,一个负责生产数据,同时解决了两者生产消费速度的不一致问题:当生产者看到仓库满了时不再生产,而是通知消费者消费,当消费者看到仓库空了时不再消费,而是通知生产者生产

自己实现阻塞队列实现生产者消费者模式(未考虑到线程中断)

public class Storage<V> {
  private int maxSize;  //仓库最大容量
  private LinkedList<V> list;  //仓库容器

  public Storage() {
    this(10);  //默认容量是10
  }

  public Storage(int maxSize) {
    this.maxSize = maxSize;
    this.list = new LinkedList<V>();
  }

  public synchronized void put(V element) {
    while (list.size() == maxSize) {  //当仓库满了就进入等待状态
      try {
        wait();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
    list.add(element);
    notify();
  }

  public synchronized V take() {
    while (list.size() == 0) {  //当仓库空了就恢复可运行状态
      try {
        wait();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
    V element = list.poll();
    notify();
    return element;
  }

  public static void main(String[] args) {  //测试代码
    Storage<Integer> storage = new Storage<>();
    Thread producer = new Thread(() -> {
      for (int i = 0; i < 50; i++) {
        System.out.println("Producer生产了一个" + i);
        storage.put(i);
      }
    });
    Thread consumer = new Thread(() -> {
      for (int i = 0; i < 50; i++) {
        try {
          Thread.sleep(500);  //模拟生产消费速度不一致
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        System.out.println("Consumer消费了一个" + storage.take());
      }
    });
    producer.start();
    consumer.start();
  }
}

使用Java中自带的阻塞队列实现生产者消费者模式(考虑到了线程中断)

public static void main(String[] args) {
  ArrayBlockingQueue storage = new ArrayBlockingQueue(10);
  Thread producer = new Thread(() -> {
    for (int i = 0; i < 50; i++) {
      try {
        System.out.println("Producer生产了一个" + i);
        storage.put(i);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  });
  Thread consumer = new Thread(() -> {
    for (int i = 0; i < 50; i++) {
      try {
        System.out.println("Consumer消费了一个" + storage.take());
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  });
  producer.start();
  consumer.start();
}

线程交替运行

同步代码块空转

该方式会导致若某个线程一直会拿到锁,也无法进行业务的执行,而是在做空操作,效率较低

public static int count;

public static void main(String[] args) {
  Object lock = new Object();
  new Thread(() -> {
    while (count < 100) {
      synchronized (lock) {
        if ((count & 1) == 0) {  //与运算高效判断奇偶数
          System.out.println(Thread.currentThread().getName() + count++);
        }
      }
    }
  }, "偶数").start();
  new Thread(() -> {
    while (count < 100) {
      synchronized (lock) {
        if ((count & 1) == 1) {  //与运算高效判断奇偶数
          System.out.println(Thread.currentThread().getName() + count++);
        }
      }
    }
  }, "奇数").start();
}
交替唤醒与休眠

拿到锁,执行业务代码,唤醒其他线程,自己进入等待状态;不存在空操作,效率高,但是交替执行的两个线程不保证一定谁先谁后

public static int count = 0;
public static int max = 100;

public static void main(String[] args) {
  Object lock = new Object();
  Runnable runnable = () -> {
    synchronized (lock) {
      while (count < max) {
        System.out.println(Thread.currentThread().getName() + count++);
        lock.notify();
        if (count == max) return;  //由于锁在两个线程之间互相传递,需要被唤醒后再次进行判断退出线程,而不是等待
        try {
          lock.wait();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }
  };
  new Thread(runnable, "奇数").start();
  new Thread(runnable, "偶数").start();
}

线程join等待

  • 当前线程等待,让出当前线程资源,交由另一个调用该方法的线程执行
    • 无参则会等待另一个线程执行结束后执行
    • 有参则会先让另一个线程执行指定时间后在争抢资源
      • 若另一个线程已经结束但时间还未结束时,当前线程在另一个线程执行结束后也会直接会执行
  • 当前线程等待期间被中断时抛出InterruptedException中断异常,同时消除当前线程的等待状态
public static void main(String[] args) throws InterruptedException {
  Runnable runnable = () -> {
    try {
      Thread.sleep(5000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "执行完毕");
  };
  Thread thread1 = new Thread(runnable);
  thread1.start();
  thread1.join();  //主线程会在该线程执行结束后执行
  //thread1.join(2500);  //主线程会在该线程执行2.5秒后执行
  //thread1.join(10000);  //超过该线程执行时后,会在该线程执行结束后就执行
  System.out.println(Thread.currentThread().getName() + "执行完毕");
}

join()方法的原理是在当前线程执行wait()方法,让当前线程处于等待状态;任何线程执行结束后都,会执行都会使用这个任意线程对象来执行notifyAll()方法,所以join()方法相当于下面代码

public static void main(String[] args) throws InterruptedException {
  Runnable runnable = new Runnable() {
    @Override
    public void run() {
      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.println(Thread.currentThread().getName() + "执行完毕");
      //this.notifyAll();  //自动执行该代码
    }
  };
  Thread thread = new Thread(runnable);
  thread.start();
  synchronized (thread) {  //主线程进入同步代码块,并且使用thread线程对象作为锁
    thread.wait();  //当thread线程执行结束时,会自动执行thread.notifyAll()方法唤醒主线程
  }
  System.out.println(Thread.currentThread().getName() + "执行完毕");
}

线程休眠

  • 线程不再占用CPU资源
  • 休眠期间被中断时不仅抛出InterruptedException中断异常,而且会清除中断标记
  • 不释放synchronizedlock
  • 使用TimeUnit.XXX.sleep()方法可指定休眠时间单位,编码上可减少对时间的换算,并且对于非法参数并不会抛出异常而是直接忽略
public static void main(String[] args) {
    ReentrantLock lock = new ReentrantLock();  //为了多个线程使用同一把锁
    Runnable runnable = () -> {
      synchronized (lock) {  //可通过注释切换不同的锁进行查看
        //lock.lock();
        System.out.println(Thread.currentThread().getName() + "获得到了锁");
        try {  //锁并不会在休眠中释放掉
          TimeUnit.SECONDS.sleep(1);  //使用SECONDS单位,休眠一秒
           System.out.println(Thread.currentThread().getName() + "线程苏醒");
        } catch (InterruptedException e) {
          e.printStackTrace();
        } finally {
          //lock.unlock();  //释放锁应该包裹在finally块中
          System.out.println(Thread.currentThread().getName() + "释放掉了锁");
        }
      }
    };
    //下面启动两个线程
    new Thread(runnable).start();
    new Thread(runnable).start();
  }

线程礼让

让当前线程释放当前CPU时间片,也就是当前线程礼让一次,线程仍处于可运行状态,随后继续按照抢占式继续执行;但是Java标准中规定JVM并不一定保证遵循线程礼让原则,也就是说当前CPU资源不紧张时,并不会一定线程礼让,所以一般开发中不适用线程礼让

此作者没有提供个人介绍
最后更新于 2021-05-01