【Java并发系列01】Thread及ThreadGroup杂谈

img { border: solid black 1px }

一、前言

  最近开始学习Java并发编程,把学习过程记录下。估计不是那么系统,主要应该是Java API的介绍(不涉及最基础的概念介绍),想要深入系统学习推荐看一本书《Java Concurrency in Practice 》(建议看英文,也可以看中文译本:《 Java 并发编程实战》)。

  并发编程的基础就是线程,所以这一篇对线程做初步了解。

二、Thread和ThredGroup的关系

  因为Thread的构造函数中有关于ThradGroup的,所以了解它们之间的关系是有必要的。ThradGroup之间的关系是树的关系,而Thread与ThradGroup的关系就像元素与集合的关系。关系图简单如下:

  其中有一点要明确一下:根线程组不需要创建,执行main方法就自动创建根线程组并将main线程放置其中

三、Thread API

3.1 基本属性

  首先应该了解线程的基本属性:

  • name:线程名称,可以重复,若没有指定会自动生成。
  • id:线程ID,一个正long值,创建线程时指定,终生不变,线程终结时ID可以复用。
  • priority:线程优先级,取值为1到10,线程优先级越高,执行的可能越大,若运行环境不支持优先级分10级,如只支持5级,那么设置5和设置6有可能是一样的。
  • state:线程状态,Thread.State枚举类型,有NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED 5种。
  • ThreadGroup:所属线程组,一个线程必然有所属线程组
  • UncaughtExceptionHandler:未捕获异常时的处理器,默认没有,线程出现错误后会立即终止当前线程运行,并打印错误。

3.2 字段摘要

  Thread类有三个字段,设置线程优先级时可使用:

  • MIN_PRIORITY:1,最低优先级
  • NORM_PRIORITY:5,普通优先级
  • MAX_PRIORITY:10,最高优先级

3.3 构造方法

  现只介绍参数最多的一个:

  Thread(ThreadGroup group, Runnable target, String name, long stackSize)

  • group:指定当前线程的线程组,未指定时线程组为创建该线程所属的线程组。
  • target:指定运行其中的Runnable,一般都需要指定,不指定的线程没有意义,或者可以通过创建Thread的子类并重新run方法。
  • name:线程的名称,不指定自动生成。
  • stackSize:预期堆栈大小,不指定默认为0,0代表忽略这个属性。与平台相关,不建议使用该属性。

3.4 方法摘要

3.4.1 静态方法

  • Thread Thread.currentThread() :获得当前线程的引用。获得当前线程后对其进行操作。
  • Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() :返回线程由于未捕获到异常而突然终止时调用的默认处理程序。
  • int Thread.activeCount():当前线程所在线程组中活动线程的数目。
  • void dumpStack() :将当前线程的堆栈跟踪打印至标准错误流。
  • int enumerate(Thread[] tarray) :将当前线程的线程组及其子组中的每一个活动线程复制到指定的数组中。
  • Map<Thread,StackTraceElement[]> getAllStackTraces() :返回所有活动线程的堆栈跟踪的一个映射。
  • boolean holdsLock(Object obj) :当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。
  • boolean interrupted() :测试当前线程是否已经中断。
  • void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) :设置当线程由于未捕获到异常而突然终止,并且没有为该线程定义其他处理程序时所调用的默认处理程序。
  • void sleep(long millis) :休眠指定时间
  • void sleep(long millis, int nanos) :休眠指定时间
  • void yield() :暂停当前正在执行的线程对象,并执行其他线程。意义不太大

3.4.2 普通方法

  • void checkAccess() :判定当前运行的线程是否有权修改该线程。
  • ClassLoader getContextClassLoader() :返回该线程的上下文 ClassLoader。
  • long getId() :返回该线程的标识符。
  • String getName() :返回该线程的名称。
  • int getPriority() :返回线程的优先级。
  • StackTraceElement[] getStackTrace() :返回一个表示该线程堆栈转储的堆栈跟踪元素数组。
  • Thread.State getState() :返回该线程的状态。
  • ThreadGroup getThreadGroup() :返回该线程所属的线程组。
  • Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() :返回该线程由于未捕获到异常而突然终止时调用的处理程序。
  • void interrupt() :中断线程。
  • boolean isAlive() :测试线程是否处于活动状态。
  • boolean isDaemon() :测试该线程是否为守护线程。
  • boolean isInterrupted():测试线程是否已经中断。
  • void join() :等待该线程终止。
  • void join(long millis) :等待该线程终止的时间最长为 millis 毫秒。
  • void join(long millis, int nanos) :等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
  • void run() :线程启动后执行的方法。
  • void setContextClassLoader(ClassLoader cl) :设置该线程的上下文 ClassLoader。
  • void setDaemon(boolean on) :将该线程标记为守护线程或用户线程。
  • void start():使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
  • String toString():返回该线程的字符串表示形式,包括线程名称、优先级和线程组。

3.4.3 作废方法

  • int countStackFrames() :没有意义不做解释。
  • void destroy() :
  • void resume() :
  • void stop() :
  • void stop(Throwable obj) :
  • void suspend() :

3.5 Thread知识方法讲解

3.5.1 setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)

  首先要了解什么是Thread.UncaughtExceptionHandler,默认来说当线程出现未捕获的异常时,会中断并抛出异常,抛出后的动作只有简单的堆栈输出。如:

public class ThreadTest{
    public static void main(String[] args) throws Exception{
        Thread t1=new Thread(new Runnable(){
            public void run(){
                int a=1/0;
            }
        });
        t1.start();
    }
}

  那么代码运行到int a=1/0;就会报错:

Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
    at yiwangzhibujian.ThreadTest$1.run(ThreadTest.java:11)
    at java.lang.Thread.run(Thread.java:662)

  这时候如果设置了Thread.UncaughtExceptionHandler,那么处理器会将异常进行捕获,捕获后就可以对其进行处理:

public class ThreadTest{
    public static void main(String[] args) throws Exception{
        Thread t1=new Thread(new Runnable(){
            public void run(){
                int a=1/0;
            }
        });
        t1.setUncaughtExceptionHandler(new UncaughtExceptionHandler(){
            @Override
            public void uncaughtException(Thread t,Throwable e){
                System.out.println("线程:"+t.getName()+"出现异常,异常信息:"+e);
            }
        });
        t1.start();
    }
}

  那么当线程抛出异常后就可以对其抓取并进行处理,最终结果如下:

线程:Thread-0出现异常,异常信息:java.lang.ArithmeticException: / by zero

  如果自己写线程,那么完全可以在run方法内,将所有代码进行try catch,在catch里做相同的操作。UncaughtExceptionHandler的意义在于不对(或者不能对)原有线程进行修改的情况下,为其增加一个错误处理器。

3.5.2 interrupt() 、interrupted() 、isInterrupted()作用

  因为stop()方法已经不建议使用了,所以如何中断一个线程就成了一个问题,一种简单的办法是设置一个全局变量needStop,如下:

@Override
public void run(){
    while(!needStop){
        //执行某些任务
    }
}

  或者需要操作耗时较长的方法内,每一步执行之前进行判断:

@Override
public void run(){
    //耗时较长步骤1
    if(needStop) return;
    //耗时较长步骤2
    if(needStop) return;
    //耗时较长步骤3
}

  这样在其他的地方将此线程停止掉,因为停止是在自己的预料下,所以不会有死锁或者数据异常问题(当然你的程序编写的时候要注意)。

  其实Thread类早就有类似的功能,那就是Thread具有中断属性。可以通过调用interrupt()方法对线程中断属性设置为true,这将导致如下两种情况:

  • 线程正常运行时,中断属性设置为true,调用其isInterrupted()方法会返回true。
  • 线程阻塞时(wait,join,sleep方法),会立即抛出InterruptedException异常,并将中断属性设置为false。此时再调用isInterrupted()会返回false。

  这样就由程序来决定怎么对线程中断进行处理。因此,上面的代码可以改成:

@Override
public void run(){
    while(!Thread.currentThread().isInterrupted()){
        //执行某些任务
    }
}
---------------------------------------------------------
@Override
public void run(){
    //耗时较长步骤1
    if(Thread.currentThread().isInterrupted()) return;
    //耗时较长步骤2
    if(Thread.currentThread().isInterrupted()) return;
    //耗时较长步骤3
}

  interrupted()的方法名容易给人一种误解,它的真实含义是

3.5.3 yield()和sleep(0)

  yield()方法的API容易给人一种误解,它的实际含义是停止执行当前线程(立即),让CPU重新选择需要执行的线程,因为具有随机性,所以也有可能重新执行该线程,通过下面例子了解:

public class ThreadTest{
    public static void main(String[] args) throws Exception{
        Thread t1=new Thread(new Runnable(){
            @Override
            public void run(){
                while(true){
                    System.out.println(1);
                    Thread.yield();
                }
            }
        });
        Thread t2=new Thread(new Runnable(){
            public void run(){
                while(true){
                    System.out.println(2);
                    Thread.yield();
                }
            }
        });
        t1.start();
        t2.start();
    }
}

  程序执行结果并不是121212而是有,有连续的1和连续的2。

  经过测试yield()和sleep(0)的效果是一样的,sleep(0)底层要么是和yield()一样,要么被过滤掉了(纯靠猜测),不过sleep(0)没有任何意义。要是真打算让当前线程暂停还是应该使用sleep(long millis,int nanos)这个方法,设置几纳秒表示下诚意,或者找到想要让步的线程,调用它的join方法更实际一些。

3.5.4 stop()、suspend()、resume()为什么不建议使用

  stop方法会立即中断线程,虽然会释放持有的锁,但是线程的运行到哪是未知的,假如在具有上下文语义的位置中断了,那么将会导致信息出现错误,比如:

@Override
public void run(){
    try{
        //处理资源并插入数据库
    }catch(Exception e){
        //出现异常回滚
    }
}

  如果在调用stop时,代码运行到捕获异常需要回滚的地方,那么将会产出错误信息。

  而suspend会将当前线程挂起,但是并不会释放所持有的资源,如果恢复线程在调用resume也需要那个资源,那么就会形成死锁。当然可以通过你精湛的编程来避免死锁,但是这个方法具有固有的死锁倾向。所以不建议使用。其他暂停方法为什么可用:

  • wait方法会释放锁,所以不会有死锁问题
  • sleep方法虽然不释放锁,但是它不需要唤醒,在使用的时候已经指定想要的睡眠时间了。

四、ThreadGroup API

4.1 基本属性

  name:当前线程的名称。

  parent:当前线程组的父线程组。

  MaxPriority:当前线程组的最高优先级,其中的线程优先级不能高于此。

4.2 构造方法

  只介绍一个构造方法:

  ThreadGroup(ThreadGroup parent, String name) :

  • parent:父线程组,若为指定则是创建该线程组的线程所需的线程组。
  • name:线程组的名称,可重复。

4.3 常用方法摘要

  API详解(中文,英文)。

  • int activeCount():返回此线程组中活动线程的估计数。
  • void interrupt():中断此线程组中的所有线程。
  • void uncaughtException(Thread t, Throwable e) :设置当前线程组的异常处理器(只对没有异常处理器的线程有效)。

4.4 ThreadGroup作用

  这个线程组可以用来管理一组线程,通过activeCount() 来查看活动线程的数量。  

  这篇博客讲解了最基本的线程及线程组,这是并发编程的根基,应该全面了解。

  未经许可禁止转载。

时间: 2024-08-14 19:17:14

【Java并发系列01】Thread及ThreadGroup杂谈的相关文章

【转】Java并发编程:Thread类的使用

Java并发编程:Thread类的使用 Java并发编程:Thread类的使用 在前面2篇文章分别讲到了线程和进程的由来.以及如何在Java中怎么创建线程和进程.今天我们来学习一下Thread类,在学习Thread类之前,先介绍与线程相关知识:线程的几种状态.上下文切换,然后接着介绍Thread类中的方法的具体使用. 以下是本文的目录大纲: 一.线程的状态 二.上下文切换 三.Thread类中的方法 若有不正之处,请多多谅解并欢迎批评指正. 请尊重作者劳动成果,转载请标明原文链接: http:/

Java并发系列[1]----AbstractQueuedSynchronizer源码分析之概要分析

学习Java并发编程不得不去了解一下java.util.concurrent这个包,这个包下面有许多我们经常用到的并发工具类,例如:ReentrantLock, CountDownLatch, CyclicBarrier, Semaphore等.而这些类的底层实现都依赖于AbstractQueuedSynchronizer这个类,由此可见这个类的重要性.所以在Java并发系列文章中我首先对AbstractQueuedSynchronizer这个类进行分析,由于这个类比较重要,而且代码比较长,为了

Java并发系列[2]----AbstractQueuedSynchronizer源码分析之独占模式

在上一篇<Java并发系列[1]----AbstractQueuedSynchronizer源码分析之概要分析>中我们介绍了AbstractQueuedSynchronizer基本的一些概念,主要讲了AQS的排队区是怎样实现的,什么是独占模式和共享模式以及如何理解结点的等待状态.理解并掌握这些内容是后续阅读AQS源码的关键,所以建议读者先看完我的上一篇文章再回过头来看这篇就比较容易理解.在本篇中会介绍在独占模式下结点是怎样进入同步队列排队的,以及离开同步队列之前会进行哪些操作.AQS为在独占模

Java并发系列[5]----ReentrantLock源码分析

在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可见性.在大多数情况下,这些机制都能很好地完成工作,但却无法实现一些更高级的功能,例如,无法中断一个正在等待获取锁的线程,无法实现限定时间的获取锁机制,无法实现非阻塞结构的加锁规则等.而这些更灵活的加锁机制通常都能够提供更好的活跃性或性能.因此,在Java5.0中增加了一种新的机制:Reentrant

Java 集合系列 01 总体框架

java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例 Java集合是java提供的工具包,包含了常用的数据结构:集合.链表.队列.栈.数组.映射等.Java集合工具包位置是java.util.*Java集合主要可以划分为4个部分:List列表.Set集合.Map映射.工具类(Itera

java io系列01之 &quot;目录&quot;

javaIO系列转载出处:http://www.cnblogs.com/skywang12345/p/io_01.html 该分类所有博文,均转载同一作者,后边不再累赘标名. java io 系列目录如下: 01. java io系列01之  "目录" 02. java io系列02之 ByteArrayInputStream的简介,源码分析和示例(包括InputStream) 03. java io系列03之 ByteArrayOutputStream的简介,源码分析和示例(包括Ou

java并发系列 - 第29天:高并发中常见的限流方式

这是java高并发系列第29篇. 环境:jdk1.8. 本文内容 介绍常见的限流算法 通过控制最大并发数来进行限流 通过漏桶算法来进行限流 通过令牌桶算法来进行限流 限流工具类RateLimiter 常见的限流的场景 秒杀活动,数量有限,访问量巨大,为了防止系统宕机,需要做限流处理 国庆期间,一般的旅游景点人口太多,采用排队方式做限流处理 医院看病通过发放排队号的方式来做限流处理. 常见的限流算法 通过控制最大并发数来进行限流 使用漏桶算法来进行限流 使用令牌桶算法来进行限流 通过控制最大并发数

Java并发编程:Thread类的使用

一.线程的状态 在正式学习Thread类中的具体方法之前,我们先来了解一下线程有哪些状态,这个将会有助于后面对Thread类中的方法的理解. 线程从创建到最终的消亡,要经历若干个状态.一般来说,线程包括以下这几个状态:创建(new).就绪(runnable).运行(running).阻塞(blocked).time waiting.waiting.消亡(dead). public enum State { /** * Thread state for a thread which has not

【Java并发系列04】线程锁synchronized和Lock和volatile和Condition

img { border: solid 1px } 一.前言 多线程怎么防止竞争资源,即防止对同一资源进行并发操作,那就是使用加锁机制.这是Java并发编程中必须要理解的一个知识点.其实使用起来还是比较简单,但是一定要理解. 有几个概念一定要牢记: 加锁必须要有锁 执行完后必须要释放锁 同一时间.同一个锁,只能有一个线程执行 二.synchronized synchronized的特点是自动释放锁,作用在方法时自动获取锁,任意对象都可做为锁,它是最常用的加锁机制,锁定几行代码,如下: //---