zoukankan      html  css  js  c++  java
  • 线程 学习教程(二): 线程相关的几个问题

    创建线程

    如何创建?

    归根到底new Thread()并调用start()是java创建并运行线程的唯一方式. Runnable,Callable 以及继承 Thread, 还有lamda表达式等类似代码其实都是线程的使用方式.

    相比较而言, 创建进程的Java方式是使用Runtime#exec(String command)方法实现.

    如何销毁?

    Java正在执行的线程只有在执行完毕, 或者抛出异常后才能停止. 也就是基本可以认为: 线程一旦开始执行就无法从外部控制其销毁.

    Thread#stop()方法用来停止线程, 但此方法过于暴力, 可能会导致资源不会正确释放, 会导致死锁, 此方法已经标记为过期.

    while(true)循环类线程可以使用interrupt()方法通过抛出InterruptedException来结束线程, 但要注意只有线程阻塞情况下才会抛出此异常, 因此, 对于非阻塞状态的线程, 是使用isInterrupted()方法来判断中断标志来结束控制循环.

    while(!isInterrupted()){
        try{
            Thread.sleep(5000);
        }catch(InterruptedException e){
            e.pringStackTrace();
            break;
        }
    }

    如何停止?

    使用状态标志变量停止方式如下:

    class TaskThread implements Runnable{
        private volatile boolean stoped = fasle; // 标识字段, 注意线程安全问题, 使用volatile保证可见性
    
        @Override
        public void run(){
            if(!stopped){
                // todo
            }
        }
    
        public void setStoped(boolean stop){
            stoped=stop;
        }
    }

    控制线程的执行顺序

    使用join()

    join()方法会阻塞调用它的线程. 方法的关键代码如下:

    public final synchronized void join(long millis){
        while (isAlive()) {
            wait(millis);
        }
    }

    可见其最终是使用Object#wait(long millis)来实现, 也就是说, 我们自己也可以使用Object#wait()来控制线程的执行顺序.

    另外的方法

    • 使用单线程池的方式
    ExecutorService singlePool = Executors.newSingleThreadExecutor()
    singlePool.submit(t1);
    singlePool.submit(t2);
    singlePool.shutdown();
    • 使用CountdownLatch
    • 使用循环检测线程
    t1.start()
    while(t1.isAlive()){
        //Thread.sleep(5);
    }
    
    t2.start();
    • 使用Thread.sleep(long millis)

    线程异常


    Java只能捕获unchecked异常. 线程异常时线程即终止.

    如何捕获?

    可以使用下面方式捕获线程抛出的异常, 这样做的话线程的异常堆栈将不会输出到System.error标准输出流.

    对于堆栈过多的场景来自定义错误handler处理, 可以防止错误堆栈导致内存耗尽. Spring boot 中的SpringApplication有相关的接口.

    Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh);

    线程池中的异常处理

    除了上面的方法, 还可以使用如下方法捕获异常.

    ThreadPoolExecutor#afterExecute(Runnable r, Throwable t)

     

    线程状态


    NEW 新建线程 new
    RUNABLE 可运行, start()
    BLOCKED 阻塞, 获取锁
    WAITING 等待
    TIME_WAITING 超时等待
    TERMINATED 终结

     

    如何获取 JVM 中所有线程状态

    • 使用外部工具: jstack pid 可以显示出所有线程.
    • 代码使用JMX:
    ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
    long[] ThreadIds = threadMXBean.getAllThreadIds();
    for(long threadId : ThreadIds){
        ThreadInfo threadInfo =ThreadMXBean.getThreadInfo(threadId);
    }

    线程同步


    synchronized 在方法和代码块上的区别,可以查看字节码文件. 关注monitorenter和monitorexit指令, 方法的话关注其flag的标志位.

    synchronized 和 ReentrantLock 的区别

    二者均可重入. ReentrantLock 多了三个功能, 可中断, 公平锁, 绑定多个Condition. ReentrantLock 性能可能更高一点, 也有更多的控制, 比如尝试获得锁, 释放等, 同时也可以绑定多个条件.

    偏向锁在 ReentrantLock 中的体现
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState(); // 当前的state
        if (c == 0) { //首次获取到锁
            if (compareAndSetState(0, acquires)) { //CAS并设置状态
                setExclusiveOwnerThread(current); //线程暂存
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) { //判定是否存储当前线程, 是的话就是重入
            int nextc = c + acquires; //状态新增
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc); //状态回写
            return true;
        }
        return false;
    }

    线程通讯

    wait() 和 notity() notifyAll() 在 Object 中定义的原因?

    一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。锁是线程争用的对象, 与线程不应该过度连接. Object上也有一些头信息, 为锁的管理提供方便.

     

    wait() 和 notity() 为什么必须在 synchronized 中执行?

     

    很简单, 多线程环境下的锁才有意义, synchronized 表明有资源争用, 即锁的争夺, 这是锁存在的前提.

     

    线程退出

    主线程退出时deamon子线程何时退出

    关于这个问题, 实际上是一个depands-on的答案, 有时候子线程会继续执行一段时间, 有时候则在主线程退出后立即退出, 一般认为主线程退出后台线程也会退出, 但并不能认为子线程一定不会执行

    Shutdown Hook 方法的使用

    方法签名: Runtime#addShutdownHook(Thread thread)

    这个钩子方法可以在线程退出时回调完成一些工作, 比如资源回收,还有一些对象的销毁工作.

     

    如何确保主线程退出前所有线程执行完毕

     

    ThreadGroup
    
    ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
    int count = threadGroup.activeCount();
    Thread[] threads = new Thread[count];
    threadGroup.enumerate(threads,true); //复制到这个数组
    for (Thread t : threads) {
        System.out.println(t.getState());
    }

     

  • 相关阅读:
    10gen发布MongoDB增量备份服务
    JSON.NET 5中的架构变更
    Snowbox 2.0 发布,POP3 邮件服务器
    资源监控工具 glances
    Druid 0.2.18 发布,阿里巴巴数据库连接池
    Groovy 更新到 2.0.8 and 2.1.3
    Apache Libcloud 0.12.4 发布,统一云计算接口
    Go1.1性能测试报告(和C差距在10%以内)
    Apache Camel 2.11.0 发布,规则引擎
    2010年01月01日0时0分 总结我的2009
  • 原文地址:https://www.cnblogs.com/xiaohuizhenyoucai/p/10762429.html
Copyright © 2011-2022 走看看