zoukankan      html  css  js  c++  java
  • Thread线程的基础知识及常见疑惑点

    引言

        相信各位道友在平时工作中已经很少直接用到Thread线程类了,现在大多是通过线程池或者一些多线程框架来操作线程任务,但我觉得还是有必要了解清楚Thread线程类中各种方法的含义,了解了底层才能更好的理解框架、应用框架。下面我就将Thread线程的相关基础点总结一二,以供观瞻。

    正文

    1、Thread线程的状态

        根据《深入理解Java虚拟机》一书的讲述,Java语言定义了五种线程状态,分别为:创建(new)、运行(Runnable)、等待(waiting)、阻塞(blocked)、结束(terminated)。而且规定,在某一个时间点,每个线程能且只能处于其中的一种状态。

    其中,运行状态又包括就绪(Ready)跟正在运行(Running),区别就是是否获得了CPU的执行时间。

    对于等待跟阻塞状态,需要着重说明一下,因为此处极易搞错,而且也是面试常被问到的点。等待状态,一般由Object.wait()、Thread.sleep()、Thread.join()、LockSupport.park()等方法以及这些方法带时间控制的同类方法实现线程的等待。而阻塞状态,一般是由于当前线程还未获取到独占锁且正在等待获取,此时称为阻塞。可以将等待看做主动的线程暂停执行,以为需要调用特定的方法线程才会等待;而阻塞可以看做是被动的线程暂定执行,因为线程在等着获取独占锁。

    2、Thread线程的相关方法

        start()方法/run()方法:有时在面试的时候,面试官会问到调用线程的start方法跟直接调用run方法有什么区别?虽然有的道友看到这里会觉得问这种问题的面试官有点很没必要,但我还是说一下。调用start方法后,最终会调用Thread类中的一个本地方法start0,这个方法可以新建一个线程来运行你的run方法,而调用run方法后只是在当前线程上运行你的run方法,并没有新线程参与。

        wait()方法/sleep()方法:请注意,这里很多人都会记错,wait方法以及跟它配套的notify/notifyAll方法,是位于顶级父类Object下的,而其他操作线程的方法都在Thread线程类下。为什么要将wait方法放在Object下呢?其实这是由wait/notify方法的实现原理决定的。wait方法调用了之后,会释放锁,并让当前线程等待,而对于java的原生锁synchronized,是隶属于一个特定对象的监视器monitor的,那这个释放的是锁谁的锁?不能是别人的,只能是调用wait方法的那个对象的。而这个锁是哪里来的?要释放锁,肯定之前加过锁,在哪里加的呢?只能是在synchronized块中给这个对象加的,所以这也解释了为什么wait/notify方法一直要跟synchronized一起用,因为它俩就是通过操作对象的锁实现的等待和唤醒。相比而言sleep方法单纯很多,它只是让当前线程睡眠一段时间,并不会涉及到对锁的操作,所以直接放在Thread类中就行。对于wait跟notify的演示如下:

     1 public static void main(String[] args) throws InterruptedException {
     2         Object obj = new Object();
     3         Thread thread = new Thread(new Runnable() {
     4             @Override
     5             public void run() {
     6                 synchronized (obj) {
     7                     try {
     8                         System.out.println("thread获取到锁,触发wait");
     9                         obj.wait();
    10                         System.out.println("wait over");
    11                     } catch (InterruptedException e) {
    12                         e.printStackTrace();
    13                     }
    14                 }
    15             }
    16         });
    17         Thread thread1 = new Thread(new Runnable() {
    18             @Override
    19             public void run() {
    20                 synchronized (obj) {
    21                     try {
    22                         System.out.println("thread1获取到锁");
    23                         Thread.sleep(1000);
    24                         System.out.println("1秒后唤醒");
    25                         obj.notify();
    26                     } catch (Exception e) {
    27                         e.printStackTrace();
    28                     }
    29                     System.out.println("notify over");
    30                 }
    31 
    32             }
    33         });
    34         thread.start();
    35         thread1.start();
    36     }

    执行结果为:

    thread获取到锁,触发wait
    thread1获取到锁
    1秒后唤醒
    notify over
    wait over

      LockSupport.park():另外还有JUC包中的park方法让当前线程等待。此方法是使用CAS实现的线程等待,不会释放锁。而park/unpark方法比wait/notify这一对好的地方在于,前者可以先unpark在park,这是线程仍然会继续执行;而对于wait/notify,则需要通过程序控制执行顺序,一定要先wait在notify/notifyAll,否则顺序反了线程就会一直等待下去,由此悲剧诞生...  比如讲上述wait/notify的代码34行35行调换一下顺序,执行结果如下所示:

    thread1获取到锁
    1秒后唤醒
    notify over
    thread获取到锁,触发wait

    仿佛云天明对程心那一千八百万年的等待

        join()/yield():对于Thread下的这两个方法,之所以放在一起讲解,就是因为这两个方法平时比较少用到,属于闲云野鹤的存在。

    yield()方法是让当前线程让步,让步的意思就是放弃执行权,即当前线程会从上述说的运行状态runnable中的running状态进入ready就绪状态,但是虚拟机不保证当前线程执行了yield方法后不会紧接着再次进去running状态,因为可能CPU分配执行时间时又分给了当前线程。所以这个方法其实一般也没啥用,因为效果不稳定。

    join()方法是将调用join的线程插入当前线程的执行过程中,即让当前线程等待,先执行完调用join的线程,再继续执行当前线程。注意join方法不会释放锁。join的演示代码如下:

     1 public class RunnableThread implements Runnable{
     2     @Override
     3     public void run() {
     4         System.out.println("runnable run");
     5         try {
     6             System.out.println("开始睡眠");
     7             Thread.sleep(5000);
     8             System.out.println("睡了5秒");
     9         } catch (Exception e) {
    10             System.out.println("runnable exception:" + e);
    11         }
    12     }
    13 
    14     public static void main(String[] args) throws InterruptedException {
    15         Object obj = new Object();
    16         Thread thread = new Thread(new RunnableThread());
    17         thread.start();
    18         thread.join();
    19         System.out.println("end");
    20     }
    21 }

    执行结果为:

    runnable run
    开始睡眠
    睡了5秒
    end

    结束语

        这次先到这里,上述说的东西,虽然很小,而且实际中不会直接用到,但是对于我们理解线程的运行机制、理解多线程框架都有好处,所以还是有必要在自己的学习地图上理解清楚。其实线程还有一个很重要的点就是线程的中断,多线程框架或者JUC包的源码中都会涉及到对线程中断的处理以及响应,这一块我会在后面梳理清楚了之后专门整理出来。最近觉得学习进入了停滞期,有点不知道从何下手,觉得需要学的东西太多。在这里,想跟各位道友讨教一下,一个资质普通的开发者,如何才能将自己的实力提升到一个比较高的层次(比如阿里的P6P7及以上?)欢迎留言赐教,在此不胜感激!

  • 相关阅读:
    Huffman树与编码
    Python引用复制,参数传递,弱引用与垃圾回收
    Git使用说明
    numpy使用指南
    Python Socket
    温故知新之 数据库的事务、隔离级别、锁
    Oracle数据库的语句级读一致性
    VirtualBox NAT方式与主机互相通信
    Linux的定时任务
    Redis学习
  • 原文地址:https://www.cnblogs.com/zzq6032010/p/10921870.html
Copyright © 2011-2022 走看看