Android开发经验谈

java 基础回顾 - 线程基础

2020-12-29  本文已影响0人  __Y_Q

1. 线程的状态

java 中线程状态分为 6 种

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

2. 线程的启动方式

start() 方法只是将一个线程进入就绪队列等待分配 CPU, 分到 CPU 后才会去调用实现的 run()方法. start() 方法不能重复调用, 否则抛出异常.

run 方法是业务逻辑实现的地方, 可以重复执行, 也可以被单独调用.

2. 线程的终止

线程的中止要么是 run 执行完成了, 要么是抛出了一个未处理的异常,从而导致线程提前结束.

不建议使用, stop()方法在终结一个线程时不会保证线程资源的正常释放, 通常是没有给线程完成资源释放工作的机会, 因此会导致程序可能工作在不确定的状态下.

线程安全的终止是其他线程通过调用某个线程的 interrupt() 方法对其进行中断操作.
中断好比对其他线程打了个招呼. 不代表线程 A 会立即停止自己的工作. 同样的 A 线程也可以不理会这种中断请求. 因为java中的线程是协作式的, 不是抢占式的. 线程内部可以通过调用isInterrupted()方法看返回是否为true/false 来判断是否被中断, 也可以调用静态方法 Thread.interrupted() 来进行判断是否中断.
interrupted()isInterrupted() 不同的是 interrupted()方法如果发现当前线程被中断, 则会清除中断标志, 也就是如果第一次调用是true, 再次调用返回的就是false, 因为之前的中断状态被清除了.

线程 A 调用了wait系列的函数, join方法或者 sleep 方法而被阻塞挂起, 这时候若是线程 B 调用线程 A 的interrupt() 方法, 线程 A 会在调用这些方法的地方抛出 InterruptedException 异常后会立即将线程 A 的中断标志位清楚, 即重新设置为 false.

public static void main(String[] args)  {
  Thread thread = new Thread(new Runnable() {
      @Override
      public void run() {
          while (true){
              if(!Thread.currentThread().isInterrupted()){
                  System.out.println("thread name:" + Thread.currentThread().getName());
              }else {
                  return;
              }
          }

      }
  });
  thread.start();
  thread.interrupt();
  System.out.println("main is over");
}   

不建议自定义一个标志位来终止线程的运行, 因为run 方法里有阻塞调用时会无法很快检测到取消标志, 线程必须从阻塞调用返回后, 才会检查这个取消标志. 这种情况下使用中断会更好. 因为一般的阻塞方法, 如sleep() 等本身就支持中断的检查.

注意: 处于死锁状态的线程无法被中断.

3. Join

将指定的线程加入到当前线程, 可以将两个交替执行的线程合并为顺序执行, 比如在线程 B 中调用了线程 A 的 join() 方法, 那么会直到线程 A 执行完毕后, 才会继续执行线程 B.

4. synchronized

关键字synchronized可以修饰方法或者以同步块的形式来进行使用, 它主要确保多个线程在同一个时刻, 只能有一个线程处于方法或者同步块中, 它爆炸了线程对变量访问的可见性和排他性. 又称为内置锁机制.

对象锁是用于对象实例方法或者一个对象实例上的, 类锁是用于类的静态方法或者一个类的 class对象上的. 类的对象实例可以有很多个, 但是每个类只有一个 class 对象, 所以不同对象实例的对象锁是互不干扰的, 但是每个类只有一个类锁.

类锁只是一个概念上的东西, 并不是真实存在的, 类锁其实锁的是每个类对应的 class 对象. 类锁与对象锁之间互不干扰.

5. 等待/通知机制

是指一个线程 A 调用了 wait() 方法进入到等待状态, 而另外一个线程 B 调用了对 notify() 或者 notifyAll() 方法. 那么线程 A 在接受到通知后就会被唤醒, 进而执行后续操作.

在调用 wait / notify / notifyAll 之前, 线程必须要获得该对象的对象级锁, 即只能在同步方法或同步块中调用 wait / notify / notifyAll 方法. 进入wait() 方法后, 当前线程就会释放锁.

尽可能使用 notifyAll(), 因为 notify() 只会唤醒一个线程, 我们无法确保被唤醒的这个线程一定就是我们需要唤醒的线程.

6. 死锁

是指两个或者两个以上的进程在执行过程中, 由于竞争资源或者彼此通信而造成的一种阻塞的现象, 若无外力作用, 它们都将无法推进下去. 此时称系统处于死锁状态.
死锁是必然发生在多操作者(M>=2) 的情况下, 增多多个资源(N>=2, 并且 N<=M). 才会发生这种情况.

例:
A 线程和 B 线程都想操作苹果和香蕉这两个对象, 但是线程 A 先拿到了苹果, 线程 B 拿到了香蕉, 线程 A 就一直在等香蕉, 线程 B 就一直在等苹果, 结果就产生了死锁.

死锁的危害

死锁的发生的四个必要条件

死锁的解除/预防/避免
理解了死锁的原因, 尤其是产生死锁的四个必要条件, 那么只要打破这四个必要条件中的任何一个, 就能够预防死锁的发生.

例如

7. 活锁

两个线程在尝试获得锁的过程中, 发生线程之间的谦让, 不断发生同一个线程总拿到同一把锁, 在尝试获得另外一把锁的时候因为拿不到, 而将本来已持有的锁释放的过程.

解决方案

8. 线程饥饿

低优先级的线程, 总拿不到执行时间.

上一篇下一篇

猜你喜欢

热点阅读