zoukankan      html  css  js  c++  java
  • Java并发专题(一)认识线程

    1.1 认识线程

      线程是轻量级进程,也是程序执行的一个路径,每一个线程都有自己的局部变量表、程序计数器(指向正在执行的指令指针)以及各自的生命周期,现代操作系统中一般不止一个线程在运行。比如说,当我们启动了一个JVM的时候,操作系统创建一个新的进程(JVM进程),JVM进程中将会创建很多线程。总而言之,线程是一个时间段的描述,是CPU工作时间段的描述。你可以想象成一个生命体,从生到死。

    1.2 线程的生命周期

      上文说过我们可以抽象的理解一个线程是一个CPU工作时间段的生命体,那么他的生命周期是如何的呢?请看图1-1。

      由图可知,线程的生命周期大致分为以下5个主要阶段

    • NEW
    • RUNNABLE
    • RUNNING
    • BLOCKED
    • TERMINATED

    1.2.1 线程的NEW状态

      用java关键字new创建一个Thread对象的时候,该线程的状态为NEW状态,准确的说,和你用关键字new创建一个普通的java对象没有什么区别,NEW状态通过start()方法进入RUNNABLE状态。有些软文上说创建一个线程的方式有2种,继承Thread,实现Runnable接口。这种说法是不准确的,也可以说是错误的。在JDK中代表线程的只有Thread这个类,所以准确的说创建线程只有一种方式,那就是构造Thread类。而实现线程要执行的业务逻辑是重写Thread的run()或者实现Runnable接口的run()。最终将Runnable实例用作构造Thread的参数。无论是哪一种,都是想将线程的控制本身和业务逻辑的运行分离开来。

    1.2.2 线程的RUNNABLE状态

      线程对象调用start()方法,才真正的在JVM进程中创建了一个线程,线程运行与否和进程一样听令与CPU调度,所以这里只是成为RUNNABLE状态,具备执行的资格,但是并没有真正的执行起来而是在等待CPU调度。

      严格来说,RUNNABLE的线程只能意外终止或者进入RUNNING状态。

    1.2.3 线程的RUNNING状态

      一旦CPU通过轮询选中了线程,那么它才真正开始执行自己的逻辑代码,正在RUNNING状态的线程也是RUNNABLE的,但是反之不成立。

      该状态可能发生如下转换

    • 直接进入TERMINATED状态,比如调用stop()方法(JDK已经不推荐使用)
    • 进入BLOCKED状态,比如调用了sleep()或者wait()方法。
    • 进入某个阻塞的IO操作
    • 获取某个锁资源
    • CPU调度使该线程放弃执行,进入RUNNABLE状态
    • 线程主动调用yield()方法,放弃执行权, 进入RUNNABLE状态

    1.2.4 线程的BLOCKED状态

      该状态可能切换至以下状态

    • 直接进入TERMINATED,比如调用stop()方法(JDK已经不推荐使用)
    • 线程阻塞操作完成,进入RUNNABLE状态,比如读取到了数据字节
    • 线程完成了指定时间的休眠,进入RUNNABLE状态
    • wait中的线程被其他线程notify/notifyAll唤醒,进入RUNNABLE状态
    • 线程获取到了锁资源,进入RUNNABLE状态
    • 线程在阻塞过程中被打断,其他线程调用了interrupt()方法,进入RUNNABLE状态

    1.2.5 线程的TERMINATED状态

      TERMINAED状态是一个最终状态,相当于一个生命体的死亡,不会切换到其他状态了,意味着整个生命周期的结束。下面这些情况将会导致线程进入TERMINATED状态。

    • 线程运行正常结束
    • 线程运行出错意外结束
    • JVM crash,导致这个进程里的所有线程都结束。

    1.3 Thread API介绍

    1.3.1 sleep()方法介绍

    public static native void sleep(long millis) throws InterruptedException;
    
    public static void sleep(long millis, int nanos) throws InterruptedException;

      sleep()方法会使当前线程进入指定毫秒数的休眠,休眠有一个非常重要的特性,它不会放弃monitor锁的所有权。可以这么记,抱着锁(你)睡觉。

    1.3.2 yield()方法介绍

    public static native void yield();

      yield()会提醒调度器我愿意放弃当前的CPU资源,如果CPU的资源不紧张,则会忽略这种提示。

      调用yield()方法会使当前线程从RUNNING状态切换到RUNNABLE状态。yield()只是一个提示,CPU调度器并不会担保每次都能满足yield提示。

    1.3.3 interrupt()方法介绍

      线程interrupt()方法是一个非常重要的API,有些方法(wait(),sleep(),join(),io操作等方法)的调用会使得当前线程进入阻塞状态,而调用当前线程的interrupt方法,就可以打断阻塞。因此这些方法有时会被称为可中断方法,打断一个线程并不等于该线程的生命周期结束,仅仅是打断了当前线程的阻塞状态。接下来我们看一个例子。

    /**
     * 打断阻塞状态测试类
     * 
     * @author GrimMjx
     */
    public class InterruptTest {
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread1 = new Thread(() -> {
                try {
                    TimeUnit.MINUTES.sleep(1);
                } catch (InterruptedException e) {
                    System.out.println("I am interrupted.");
                }
            });
    
            thread1.start();
    
            TimeUnit.SECONDS.sleep(2);
            thread1.interrupt();
        }
    }

      上面的代码先创建出一个线程,休眠1分钟,但是main线程在休眠2秒钟之后调用interrupt()方法打断thread1。

      interrupt()到底做了什么事情呢?在一个线程内部存在一个名为interrupt flag的标识,如果一个线程被interrupt,那么它的flag会被设置,但是如果当前线程正在执行可中断方法被阻塞时,调用interrupt方法将其中断,反而会导致flag被清除。

    1.3.4 join()方法介绍

    public final void join() throws InterruptedException;

      join某个线程A,会使当前线程进入等待状态,直到线程A结束生命周期,或者到达给定的时间,那么在此期间,当前线程都是出于BLOCKED状态的,而不是线程A。接下来我们看一个例子。

    /**
     * Join方法测试类
     *
     * @author GrimMjx
     */
    public class JoinTest {
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread = create("thread1");
            thread.start();
    
            //执行join方法
            thread.join();
    
            //主线程开始工作
            System.out.println(Thread.currentThread().getName() + " is processing");
            TimeUnit.SECONDS.sleep(1);
        }
    
        private static Thread create(String name) {
            return new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " is processing");
            }, name);
        }
    }

      join()会使当前线程永远等待下去,直到期间被另外的线程中断,或者join的线程执行结束。concurrent包里的CountDownLatch和CyclicBarrier都可以实现和join()方法一样的功能,当某些线程达到一个点做一件事情。这个有兴趣的同学可以自己去学习使用。并掌握其中的不同点和共同点。

  • 相关阅读:
    PPTP服务器的端口
    Linux ln命令
    Git 学习笔记回到过去版本
    Iptables中SNAT和MASQUERADE的区别
    转移虚拟机后ubuntu network available SIOCSIFADDR: No such device
    用iptables做IP的静态映射
    软件项目管理
    需求工程
    软件工程——理论、方法与实践 之 软件实现
    软件工程——理论、方法与实践 之 软件工程中的形式化方法
  • 原文地址:https://www.cnblogs.com/GrimMjx/p/9595227.html
Copyright © 2011-2022 走看看