本文应注重掌握如下知识点:

  1. 线程组的使用
  2. 如何切换线程状态
  3. SimpleDataFormat 类与多线程的解决办法
  4. 如何处理线程的异常

1.线程的状态

线程对象在不同运行时期有不同的状态,状态信息就处于State枚举类中,如图所示:

线程状态

  1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。

  2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。

    线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。

  3. 阻塞(BLOCKED):表示线程阻塞于锁。

  4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。

  5. 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。

  6. 终止(TERMINATED):表示该线程已经执行完毕。

调用与线程有关的方法是造成线程状态改变的主要原因,其关系如图所示:(图片来源于网络)

2.线程组

可以把线程归属到某一个线程组中,线程组中可以有线程对象,也可以有线程组,组中还可以有线程。

下面看下线程组的使用示例:

public class Group implements Runnable {

    public static void main(String[] args) {
        Group runnable = new Group();
        ThreadGroup threadGroup = new ThreadGroup("我的线程组");
        Thread threadA = new Thread(threadGroup,runnable);
        Thread threadB = new Thread(threadGroup,runnable);
        threadA.start();
        threadB.start();
        System.out.println("活动的线程"+threadGroup.activeCount());
        System.out.println("线程组的名称"+threadGroup.getName());
    }

    @Override
    public void run() {
        while (true){
            System.out.println("Thread-Name: "+Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:

活动的线程2
线程组的名称我的线程组
Thread-Name: Thread-0
Thread-Name: Thread-1

控制台中的信息表明有2个线程在名为“我的线程组”下活动。

上面的线程树结构图中显示,所有的线程与线程组都在系统线程组下,下面演示如何创建具有多级关联关系的线程结构,并获取相关线程信息。

示例代码:

public class Group implements Runnable {

    public static void main(String[] args) {
        ThreadGroup threadGroupMain = Thread.currentThread().getThreadGroup();
        Group runnable = new Group();
        ThreadGroup threadGroup = new ThreadGroup(threadGroupMain,"我的线程组");
        Thread threadA = new Thread(threadGroup,runnable);
        Thread threadB = new Thread(threadGroup,runnable);
        threadA.start();
        threadB.start();
        System.out.println("系统线程组的名字:"+Thread.currentThread().getThreadGroup().getName());
        System.out.println("系统线程组中有多少子线程:"+Thread.currentThread().getThreadGroup().activeCount());
        Thread[] threads1 = new Thread[threadGroupMain.activeCount()];
        threadGroupMain.enumerate(threads1);
        System.out.println("这些子线程具体是:");
        for (int i = 0; i < threads1.length; i++) {
            System.out.println(threads1[i].getName());
        }
        System.out.println("系统线程组中有多少子线程组:"+Thread.currentThread().getThreadGroup().activeGroupCount());
        ThreadGroup[] listGroup = new ThreadGroup[Thread.currentThread().getThreadGroup().activeGroupCount()];
        //enumerate方法将子线程组以复制的形式拷贝到数组中,并返回拷贝的数量
        Thread.currentThread().getThreadGroup().enumerate(listGroup);
        System.out.println("子线程组的名字是:"+listGroup[0].getName());
        Thread[] threads = new Thread[listGroup[0].activeCount()];
        listGroup[0].enumerate(threads);
        System.out.println("子线程组中的线程有:");
        for (int i = 0; i < threads.length; i++) {
            System.out.println(threads[i].getName());
        }
    }

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:

系统线程组的名字:main
系统线程组中有多少子线程:4
这些子线程具体是:
main
Monitor Ctrl-Break
Thread-0
Thread-1
系统线程组中有多少子线程组:1
子线程组的名字是:我的线程组
子线程组中的线程有:
Thread-0
Thread-1

需要说明的是,在实例化一个ThreadGroup线程组x时如果不指定所属的线程组,则x线程组自动归属到当前线程对象所属的线程组中,也就是隐式的当前线程组中添加了一个子线程组。

线程组批量操作

通过将线程归属到线程组中,当调用线程组ThreadGroup的interrupt()方法时,可以将该组中的所有正在运行的线程批量停止。

3.SimpleDataFormat非线程安全

SimpleDataFormat主要负责日期的转换与格式化,但在多线程的环境中,使用此类容易造成数据及处理的不准确,因为SimpleDataFormat并不是安全的。

解决方法有两种,一是每个线程都new一个新的SimpleDataFormat实例对象;二是利用ThreadLocal类将SimpleDataFormat对象绑定到线程上。

4.线程中出现异常的处理

在 Java 的多线程技术中,可以对多线程中的异常进行“捕捉”,使用的是 UncaughtExceptionHandler类,从而可以对发生的异常进行有效的处理。

thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
     @Override
     public void uncaughtException(Thread t, Throwable e) {
          System.out.println("线程"+t.getName()+"出现了异常");
          e.printStackTrace();
     }
});

方法setUncaughtExceptionHandler()是给指定的线程对象设置的异常处理器。在Thread类中还可以使用setDefaultUncaughtExceptionHandler()方法对所有线程对象设置异常处理器。示例代码如下:

MyThread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
       @Override
       public void uncaughtException(Thread t, Throwable e) {
           System.out.println("线程"+t.getName()+"出现了异常");
           e.printStackTrace();
       }
});

方法setDefaultUncaughtExceptionHandler()的作用是为指定的线程类的所有线程对象设置默认的异常处理器。

5.线程组中出现异常的处理

使用重写uncaughtException方法处理组内线程的异常时,每个线程内部不要有异常catch语句,如果有catch语句,则public void uncaughtException(Thread t, Throwable e) 方法不执行。

ThreadGroup group = new ThreadGroup(""){
      @Override
      public void uncaughtException(Thread t, Throwable e) {
             super.uncaughtException(t, e);
             //一个线程出现异常,中断组内所有线程
             this.interrupt();
      }
};

前面介绍了线程对象的异常处理,线程类的异常处理,线程组的异常处理。将它们放一起会出现什么效果呢?

示例代码:

public class MyThread{

    public static void main(String[] args) {
        ThreadGroup threadGroup = new ThreadGroup("ThreadGroup"){
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println("线程组的异常处理");
                super.uncaughtException(t, e);
            }
        };
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println("线程类的异常处理");
            }
        });
        Thread thread = new Thread(threadGroup,"Thread"){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"执行");
                int i= 2/0;
            }
        };
        thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println("线程对象的异常处理");
            }
        });
        thread.start();
    }

}

运行结果:

Thread执行
线程对象的异常处理

注释掉线程对象的异常处理之后,再次运行:

public class MyThread{

    public static void main(String[] args) {
        ThreadGroup threadGroup = new ThreadGroup("ThreadGroup"){
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println("线程组的异常处理");
                super.uncaughtException(t, e);
            }
        };
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println("线程类的异常处理");
            }
        });
        Thread thread = new Thread(threadGroup,"Thread"){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"执行");
                int i= 2/0;
            }
        };
//        thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
//            @Override
//            public void uncaughtException(Thread t, Throwable e) {
//                System.out.println("线程对象的异常处理");
//            }
//        });
        thread.start();
    }

}

运行结果:

Thread执行
线程组的异常处理
线程类的异常处理

6.文末总结

本文弥补了前面几个文章的技术空白点。到此,Java多线程编程核心技术的学习告一段落。

参考

《Java多线程编程核心技术》高洪岩著

扩展

Java多线程编程核心技术(一)Java多线程技能

Java多线程编程核心技术(二)对象及变量的并发访问

Java多线程编程核心技术(三)多线程通信

Java多线程核心技术(四)Lock的使用

Java多线程核心技术(五)单例模式与多线程

Java多线程核心技术(六)线程组与线程异常的更多相关文章

  1. “全栈2019”Java多线程第六章:中断线程interrupt()方法详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  2. Java多线程核心技术(五)单例模式与多线程

    本文只需要考虑一件事:如何使单例模式遇到多线程是安全的.正确的 1.立即加载 / "饿汉模式" 什么是立即加载?立即加载就是使用类的时候已经将对象创建完毕,常见的实现办法就是直接 ...

  3. Java多线程核心技术(四)Lock的使用

    本文主要介绍使用Java5中Lock对象也能实现同步的效果,而且在使用上更加方便. 本文着重掌握如下2个知识点: ReentrantLock 类的使用. ReentrantReadWriteLock ...

  4. 【Java基础】Java多线程之线程组和线程池

    在上一篇文章中,讲述了线程的基本概念和用法,这里将继续讲述线程组和线程池的一些东西. 线程组:java.lang.ThreadGroup 1. 线程组的介绍 线程组表示一个线程的集合.此外,线程组也可 ...

  5. 从ConcurrentHashMap的演进看Java多线程核心技术 Java进阶(六)

    本文分析了HashMap的实现原理,以及resize可能引起死循环和Fast-fail等线程不安全行为.同时结合源码从数据结构,寻址方式,同步方式,计算size等角度分析了JDK 1.7和JDK 1. ...

  6. Java多线程系列--“基础篇”06之 线程让步

    概要 本章,会对Thread中的线程让步方法yield()进行介绍.涉及到的内容包括:1. yield()介绍2. yield()示例3. yield() 与 wait()的比较 转载请注明出处:ht ...

  7. Java多线程系列--“基础篇”07之 线程休眠

    概要 本章,会对Thread中sleep()方法进行介绍.涉及到的内容包括:1. sleep()介绍2. sleep()示例3. sleep() 与 wait()的比较 转载请注明出处:http:// ...

  8. Java多线程系列--“基础篇”10之 线程优先级和守护线程

    概要 本章,会对守护线程和线程优先级进行介绍.涉及到的内容包括:1. 线程优先级的介绍2. 线程优先级的示例3. 守护线程的示例 转载请注明出处:http://www.cnblogs.com/skyw ...

  9. Java多线程(三)如何创建线程

    点我跳过黑哥的卑鄙广告行为,进入正文. Java多线程系列更新中~ 正式篇: Java多线程(一) 什么是线程 Java多线程(二)关于多线程的CPU密集型和IO密集型这件事 Java多线程(三)如何 ...

随机推荐

  1. hdu-2018题(母牛问题)

    HDU-2018题/*有一头母牛,它每年年初生一头小母牛.每头小母牛从第四个年头开始,每年年初也生一头小母牛.请编程实现在第n年的时候,共有多少头母牛?Input输入数据由多个测试实例组成,每个测试实 ...

  2. python 正则表达式中反斜杠(\)的麻烦和陷阱

    这里是一点小心得:由于下面两个原因,在正则表达式中使用反斜杠就会产生了一个双重转换的问题. (1).python自身处理字符串时,反斜杠是用于转义字符 (2).正则表达式也使用反斜杠来转义字符     ...

  3. MySQL 5.7安装指南

    1.下载 1)进入官网下载5.7.23压缩包 下载地址:https://dev.mysql.com/downloads/mysql/5.7.html#downloads 2.安装与配置 1)将下载的压 ...

  4. CentOS7中启动Tomcat后,8080端口不能被外部访问的解决办法。

    运行:/sbin/iptables -I INPUT -p tcp --dport 8080 -j ACCEPT

  5. SQL SERVER数据库级的触发器

    CREATE TRIGGER [Object_Change_Trigger_DDL] ON database FOR DROP_TABLE AS DECLARE @EventData AS xml; ...

  6. 平衡二叉树(Balanced Binary Tree 或 Height-Balanced Tree)又称AVL树

    平衡二叉树(Balanced Binary Tree 或 Height-Balanced Tree)又称AVL树 (a)和(b)都是排序二叉树,但是查找(b)的93节点就需要查找6次,查找(a)的93 ...

  7. May 23. 2018 Week 21st Wednesday

    You should love and take care of yourself because after all, it is your own life. 要学会爱自己,照顾自己,毕竟生活是你 ...

  8. js获取请求地址后面带的参数

    浏览器输入页面地址的时候在后面带有请求参数, 页面加载后需要获取携带的参数, 可以使用js, 在页面加载js的时候获取参数 http://localhost:8080/demo/index.html? ...

  9. Windows 7 X64 SQL Server 2000 企业管理器无法建立新表

    问题现象: 当建立新表时会出现一个空白的窗口,敲击键盘输入后就会出现应用出错,然后退出. 解决方案: 选中某个已经存在的表,再点右键,新建表就可以正常操作了.

  10. ES5-ES6-ES7_集合Set和Map

    集合的概念 集合是一组无序且唯一(元素不能重复)的项组成的.这个数据结构使用了与有限集合相同的数学概念,应用在计算机的数据结构中 特点:key和value相同,没有重复的value Set集合 ES6 ...