美文网首页
Thread 基础知识

Thread 基础知识

作者: 爱吃鱼的KK | 来源:发表于2017-01-12 23:52 被阅读137次
线程

线程(Thread)是java程序运行的基本调度单元; 在进行JUC的源码分析之前, 想回顾一下Thread的基本属性, 及方法;

1. 线程 State:
/**
 * Created by xjk on 1/12/17.
 */
public enum State {
    /** 线程刚刚创建时的状态, 马上到 RUNNABLE */
    NEW,
    
    /** 线程初始化OK, 开始执行任务(run) */
    RUNNABLE,
    
    /**
     * 阻塞状态, 千万别和WAITING状态混淆
     * 这种状态是线程在等待 JVM monitor lock(通俗一点 就是等待执行 synchronous 里面的代码)
     * 这和 LockSupport 没半毛钱关系
     */
    BLOCKED,
    
    /**
     * 线程的等待状态, 导致线程进入这种状态通常是下面三个方法
     * 1. Object.wait()
     * 2. Thread.join()
     * 3. LockSupport.park()
     */
    WAITING,
    
    /**
     * 这也是线程的等待状态, 和WAITING差不多, 只是这个有timeout而已, 通常由下面四种方法导致
     * 1. Object.wait(long timeout)
     * 2. Thread.join(long timeout)
     * 3. LockSupport.parkNanos(long timeout)
     * 4. LockSupport.parkUntil(long timeout)
     */
    TIMED_WAITING,

    /**
     * 线程执行ok
     */
    TERMINATED
}

这些状态何时有用, 我又何时见到它们呢? 对, 一般线程出问题就会想到它们了(比如 程序死锁, 直接运行 jstack 查看线程堆栈, 就能大体判断问题出在哪个线程, 哪段代码)

2. 常用方法
/** 等待 */
Object.wait();
/** 通知 */
Object.notify();
/** 悬挂 */
Thread.suspend();
/** 重用 */
Thread.resume();
/** 等待x线程执行后, 当前线程再执行 */
Thread.join();
/**
 * A hint to the scheduler that the current thread is willing to yield
 * its current use of a processor. The scheduler is free to ignore this
 * hint.
 * 这段英语大体意思: 给调度器发送信息, 当前线程推出CPU调度(这个不是指当前线程不执行任务)
 * 调用这个方法后, 当前线程会先推出任务调度, 然后再重新抢夺CPU, 但能不能抢到就不一定了
 * 通产用于, 当前线程占用较多资源, 但任务又不紧急的情况(concurrent包中的源码会提及)
 */
Thread.yield();

这几个是线程的基本用法, 但现在用得比较少, 为啥?

  1. jdk中有了更好用的 concurrent 包开进行多线程开发
  2. 使用这些方法有时会出现奇怪的事件(尤其和 concurrent 包中的工具类混用, 匪夷所思)
    ps: 若想用这些方法来写 Future, Promise 等工具类 可以 参考Netty 4.x系列
3. 线程中断

线程中断是java中线程协作的重要机制, 而所谓的中断其实只是给线程设置中断标示, 并且唤醒正在waiting状态的线程;
线程中断相关的主要有三个方法:


/**
 * 作用: 中断线程,
 * 若线程 sleep 或 wait 时调用此方法,
 * 则抛出 InterruptedException 异常, 并且会清除中断标记
 * (ps 重点来了, 若通过 LockSupport阻塞线程, 则不会抛出异常, 并且不会清除线程的中断标记, 这在 concurrent 包里面充分利用了这个机制)
 *
 * 比如先通过 LockSupport.park(this) 来中断, 而后其他线程释放lock时, 唤醒这个线程, 这时再调用 Thread.interrupted() 返回中断标示(调用此方法会清除中断标示)
 *  这时外面的函数会根据 parkAndCheckInterrupt() 函数的返回值判断线程的唤醒是被 interrupted 还是正常的唤醒(LockSupport.unpark()) 来决定后续的策略
 * private final boolean parkAndCheckInterrupt() {
 *     LockSupport.park(this);
 *     return Thread.interrupted();
 * }
 */
Thread.interrupt();

/**
 * 判断当前的线程是否中断, 返回 true/false
 */
Thread.isInterrupted();

/**
 * 判断当前的线程是否中断, 并且清除中断标示(注意这里是 interrupted, 和上面的 interrupt是不一样的)
 */
Thread.interrupted();

线程的中断是 Java 并发开发中非常重要的机制, concurrent 包中的很多工具类的方法都是通过这个机制来安全退出;

下面来段代码示例一下:


import org.apache.log4j.Logger;

import java.util.concurrent.locks.LockSupport;

/**
 * Created by xjk on 1/13/17.
 */
public class ThreadTest {

    private static final Logger logger = Logger.getLogger(ThreadTest.class);

    public static void main(String[] args) throws Exception{

        Thread t1 = new Thread(){
            @Override
            public void run() {
                while(true){
                    if(Thread.currentThread().isInterrupted()){
                        logger.info("线程中断, 退出loop");
                        break;
                    }

                    try {
                        Thread.sleep(5*1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        logger.info("线程在 waiting 状态时收到中断信息");
                        logger.info("此时线程中断标示: " + Thread.currentThread().isInterrupted());
                        // 再次点用线程中断, 这时就又有中断标示
                        Thread.currentThread().interrupt();
                    }

                    Thread.yield();
                }
                logger.info("1. 此时线程中断标示: " + Thread.currentThread().isInterrupted());

                // 再次调用程序阻塞, 看看是否有用
                LockSupport.park(this);
                logger.info("2. 此时线程中断标示: " + Thread.currentThread().isInterrupted());
                try {
                    Thread.currentThread().sleep(5*1000);
                } catch (InterruptedException e) {
                    logger.info("在线程中断时调用sleep 抛异常");
                    e.printStackTrace();
                }


                logger.info("3. 此时线程中断标示: " + Thread.currentThread().interrupted());

            }
        };

        t1.start();
        Thread.sleep(2 *1000);
        t1.interrupt();
    }
}

这段代码主要测试线程中断的几个函数功能, 建议自己写一下, 在运行程序时, 会发现 程序中的'LockSupport.park(this);' 没起作用, 而且还没清除中断标记; 但此时调用 Thread.sleep(long timeout), 则会因为中断标示的存在而抛出异常(抛出异常后中断标示也被清除);
执行结果

java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at com.lami.tuomatuo.search.base.concurrent.thread.ThreadTest$1.run(ThreadTest.java:26)
[2017-01-13 22:23:03,903] INFO  Thread-0 (ThreadTest.java:29) - 线程在 waiting 状态时收到中断信息
[2017-01-13 22:23:03,906] INFO  Thread-0 (ThreadTest.java:30) - 此时线程中断标示: false
[2017-01-13 22:23:03,907] INFO  Thread-0 (ThreadTest.java:21) - 线程中断, 退出loop
[2017-01-13 22:23:03,907] INFO  Thread-0 (ThreadTest.java:37) - 1. 此时线程中断标示: true
[2017-01-13 22:23:03,908] INFO  Thread-0 (ThreadTest.java:41) - 2. 此时线程中断标示: true
[2017-01-13 22:23:03,909] INFO  Thread-0 (ThreadTest.java:45) - 在线程中断时调用sleep 抛异常
[2017-01-13 22:23:03,909] INFO  Thread-0 (ThreadTest.java:50) - 3. 此时线程中断标示: false
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at com.lami.tuomatuo.search.base.concurrent.thread.ThreadTest$1.run(ThreadTest.java:43)
4. Daemon进程

Daemon线程: 守护线程通常是在后台默默干一些苦活, 比如垃圾回收; 守护线程有个特点, 就是当那些daemon=false的线程都退出, 则daemon也退出
直接上代码:

import org.apache.log4j.Logger;

/**
 * Created by xjk on 1/13/17.
 */
public class DaemonThreadTest {

    private static final Logger logger = Logger.getLogger(DaemonThreadTest.class);

    static Thread t1 = new Thread(){
        @Override
        public void run() {
            while(true){
               logger.info("I am alive");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            logger.info("我要退出程序了");
        }
    };

    public static void main(String[] args) throws Exception{
        t1.setDaemon(true);
        t1.start();

        Thread.sleep(3 * 1000);

        logger.info("main 方法执行OK");

    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        logger.info("DaemonThreadTest 退出");
    }

}

线程t1 daemon=true, 所以在主线程退出后, 自己也退出, 自己在运行时,可以将 "t1.setDaemon(true)" 注释掉再看看效果
ps:

  1. finalize在java中不一定会执行
  2. daemon 默认值是 false

console:

[2017-01-13 22:43:27,605] INFO  Thread-0 (DaemonThreadTest.java:16) - I am alive
[2017-01-13 22:43:28,612] INFO  Thread-0 (DaemonThreadTest.java:16) - I am alive
[2017-01-13 22:43:29,615] INFO  Thread-0 (DaemonThreadTest.java:16) - I am alive
[2017-01-13 22:43:30,606] INFO  main (DaemonThreadTest.java:38) - main 方法执行OK

Process finished with exit code 0

总结: 线程中断机制在整个并发编程中起着非常大的作用, 尤其和 LockSupport 配合使用

参考资料:
skywang12345 线程 interrupt
zhanjindong LockSupport和Interrupt (这篇写得相当好👌)

相关文章

  • 多线程

    _thread模块 基础知识 开启一个新线程 _thread.start_new_thread(functio...

  • Thread基础知识

    Thread.join() 将线程A加入到当前执行线程中,只有当线程A执行完毕,当前线程才能继续执行; join方...

  • Thread 基础知识

    线程 线程(Thread)是java程序运行的基本调度单元; 在进行JUC的源码分析之前, 想回顾一下Thread...

  • Python爬虫速度很慢?并发编程了解一下吧

    文章目录 前言 基础知识 GIL 多线程 创建Thread 对象 自定义类继承 Thread 私信小编01即可获取...

  • android面试/笔试题归纳2

    第二波,继续继续,大家多多指教。 题目 自定义view service的基础知识 thread在appliciti...

  • Java并发整理

    读Java并发专题总结 一. 基础知识 新建线程继承Thread类,重写run方法实现Runable接口实现Cal...

  • (Java) 查看线程状态

    参考资料 (豆瓣链接)Java核心技术 卷I 基础知识 第10版 英文版 的 14.3 Thread States...

  • Java基础知识复盘-多线程

    一、Java基础知识复盘-多线程 1.1.线程的创建和使用 方式一:继承Thread类 创建一个类继承于Threa...

  • python多线程之二——threading模块

    上一篇文章讲了python多线程的基础知识和thread模块,这一篇着重讲解一下threading模块 threa...

  • Android 计时的两种思路

    1.Android中计时 趁最近两周不忙,自己支配的时间比较多,正好查漏补缺,这两天看了些Thread的基础知识,...

网友评论

      本文标题:Thread 基础知识

      本文链接:https://www.haomeiwen.com/subject/nhmjbttx.html