java 基础回顾 - 线程基础

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. 线程的启动方式

  • xxx extends Thread 然后调用 xxx.start
  • xxx implements Runnable 然后交给 Thread 运行.
  • 区别: Thread 才是 Java 中对线程唯一抽象, Runnable 只是对任务(业务逻辑)的抽象. Thread可以接受任意一个 Runnable 的实例并执行.

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

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

2. 线程的终止

  • 线程自然终止

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

  • 使用 stop() 方法

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

  • 使用 interrupt中断方式.

线程安全的终止是其他线程通过调用某个线程的 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. 等待/通知机制

  • wait / notify / notifyAll

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

  • notify
    唤醒一个等待的线程, 唤醒的前提是该线程获取到了锁. 没有获得锁就会重新进入 waiting 状态.
  • nofifyAll
    唤醒所有等待状态的线程.
  • wait
    调用该方法的线程将会进入到 waiting 状态, 只有等待另外线程的唤醒或者被中断才会返回. 注意: 调用 wait() 方法后会释放对象的锁.

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

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

6. 死锁

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

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

死锁的危害

  • 线程不工作了, 但是整个进程又是活着的.
  • 没有任何异常信息供我们检查.
  • 一旦程序发生了死锁, 是没有任何办法恢复的, 只有重启程序.

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

  • 互斥
    指线程对分配到的资源进行排他性使用. 即在一段时间内某资源只由一个线程占用. 如果此时还有其他线程请求资源, 则请求者只能等待, 直至占有资源的进程使用完毕释放.

  • 请求和保持
    指线程已经保持至少一个资源, 但又提出了新的资源使用请求, 而该新的资源已经被其他线程占有, 此时请求线程阻塞, 但是又对已持有的资源保持不释放.

  • 不剥夺
    指线程已获得的资源, 在未使用完之前, 不能被剥夺, 只能在使用完时由自己释放.

  • 环路等待
    指在发生死锁时, 必然存在一个线程资源的环形链, 即线程集合{T0, T1, T2...Tn}中的 T0 正在等待一个 T1 占用的资源, 而 T1 呢又在等在 T2 占用的资源, Tn又在等待 T0 占用的资源.

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

例如

  • 当一个线程独占有一份资源后, 又申请了一个独占资源而无法满足的情况下, 就退出原有的资源.
  • 采用资源预先分配策略, 即线程运行前申请全部资源, 满足则运行, 不然就等待. 这样就不会有占有且申请的情况发生.
  • 实现资源有序分配策略, 对所有资源实现分类编号, 所有线程只能采用按序号递增的形式申请资源.

7. 活锁

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

解决方案

  • 每个线程休眠随机数, 错开拿锁的时间.

8. 线程饥饿

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容