线程在不同的运行时期存在不同的状态,状态信息存在于State枚举类中。
在调用与线程有关的方法后,线程会进入不同的状态。
线程可以处于NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED六种状态。
package threadstate;
public class MyThread extends Thread{
public MyThread()
{
System.out.println("构造方法中的状态 Thread.currentThread().getState()="+Thread.currentThread().getState());
System.out.println("构造方法中的状态 this.getState()="+this.getState());
}
@Override
public void run() {
System.out.println("run方法中的状态:"+ Thread.currentThread().getState());
}
}
package threadstate.test;
import threadstate.MyThread;
public class Run {
public static void main(String[] args){
try {
MyThread myThread = new MyThread();
System.out.println("main方法中的状态1:"+myThread.getState());
Thread.sleep(1000);
myThread.start();
Thread.sleep(1000);
System.out.println("main方法中的状态2:"+myThread.getState());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
该状态代表线程执行了Thread.sleep()方法,呈等待状态,等待时间达到后继续向下运行
创建包threadstate,在包下创建类MyThread2
package threadstate;
public class MyThread2 extends Thread{
@Override
public void run() {
try {
System.out.println("begin sleep");
Thread.sleep(10000);
System.out.println("end sleep");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
创建包threadstate.test,在包下创建类Run2
package threadstate.test;
import threadstate.MyThread2;
public class Run2 {
public static void main(String[] args){
try {
MyThread2 myThread = new MyThread2();
myThread.start();
Thread.sleep(1000);
System.out.println("main方法中的状态:"+myThread.getState());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
即在启动MyThread2线程‘’后,主线程休眠,让MyThread2线程的run方法先执行到休眠,再通过主线程打印出MyThread2线程的状态,此时MyThread2线程为TIMED_WAITING状态。
该状态出现在某一个线程在等待锁的时候。
创建包threadstate.service,在包下创建类MyService
package threadstate.service;
public class MyService{
synchronized static public void serviceMethod() {
try {
System.out.println(Thread.currentThread().getName()+"进入业务方法!");
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
创建包threadstate,在包下创建线程类MyThread3
package threadstate;
import threadstate.service.MyService;
public class MyThread3 extends Thread{
@Override
public void run() {
MyService.serviceMethod();
}
}
创建包threadstate,在包下创建线程类MyThread4
package threadstate;
import threadstate.service.MyService;
public class MyThread4 extends Thread{
@Override
public void run() {
MyService.serviceMethod();
}
}
创建包threadstate.test,在包下创建类Run3
package threadstate.test;
import threadstate.MyThread3;
import threadstate.MyThread4;
public class Run3 {
public static void main(String[] args){
try {
MyThread3 myThread3 = new MyThread3();
myThread3.setName("a");
myThread3.start();
Thread.sleep(1000);
MyThread4 myThread4 = new MyThread4();
myThread4.setName("b");
myThread4.start();
Thread.sleep(1000);
System.out.println("main方法中mythread4的状态:"+myThread4.getState());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
即MyThread3启动后,运行run()方法去调用MyService类的同步方法serviceMethod后,又启动MyThread4,运行run方法取调用MyService类的同步方法serviceMethod,但是此时MyThread3正在占用锁,所以MyThread4处于阻塞状态。所以此时打印MyThread4的状态时为BLOCKED。
该状态是线程执行了Object.wait()方法后所处在的状态。
创建包threadstate.service,在包下创建类Lock
package threadstate.service;
public class Lock {
public static final Byte lock = new Byte("0");
}
创建包threadstate,在包下创建线程类MyThread5
package threadstate;
import threadstate.service.Lock;
public class MyThread5 extends Thread{
@Override
public void run() {
try {
synchronized (Lock.lock)
{
Lock.lock.wait();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
创建包threadstate.test,在包下创建线程类Run4
package threadstate.test;
import threadstate.MyThread5;
public class Run4 {
public static void main(String[] args) {
try {
MyThread5 myThread5 = new MyThread5();
myThread5.start();
Thread.sleep(1000);
System.out.println("main 方法中的myThread5状态"+myThread5.getState());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
即启动线程类MyThread5,也就是运行run方法,在run方法中使用synchronized关键字同步代码块中调用Lock类的lock属性的wait方法,最后mian线程打印MyThread5的状态为WAITING。
线程组中可以有线程对象、线程,类似于树的形式。
线程组的作用是可以批量地管理线程或线程对象,有效地对线程或线程对象进行组织。
一级关联就是父对象中有子对象,但不创建子孙对象。
创建包threadgroup.thread,在包下创建线程类ThreadA
package threadgroup.thread;
public class ThreadA extends Thread{
@Override
public void run() {
try {
while(!Thread.currentThread().isInterrupted())
{
System.out.println("ThreadName="+Thread.currentThread().getName());
Thread.sleep(3000);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
创建包threadgroup.thread,在包下创建线程类ThreadB
package threadgroup.thread;
public class ThreadB extends Thread{
@Override
public void run() {
try {
while(!Thread.currentThread().isInterrupted())
{
System.out.println("ThreadName="+Thread.currentThread().getName());
Thread.sleep(3000);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
创建包threadgroup.test,在包下创建类Run
package threadgroup.test;
import threadgroup.thread.ThreadA;
import threadgroup.thread.ThreadB;
public class Run {
public static void main(String[] args) {
ThreadA threadA = new ThreadA();
ThreadB threadB = new ThreadB();
ThreadGroup threadGroup = new ThreadGroup("ThreadGroup");
//将ThreadA和ThreadGroup通过Thread关联
Thread thread1 = new Thread(threadGroup, threadA);
Thread thread2 = new Thread(threadGroup, threadB);
thread1.start();
thread2.start();
System.out.println("活动的线程数为:"+threadGroup.activeCount());
System.out.println("线程组的名称为:"+threadGroup.getName());
}
}
即将线程对象ThreadA和ThreadB与同一个线程组对象ThreadGroup用Thread进行关联。再启动Thread即可。从而形成线程组。
多级关联就是父对象中有子对象,子对象中再创建子对象,也就是子孙对象。
创建包threadgroup.test,在包下创建类Run2
package threadgroup.test;
public class Run2 {
public static void main(String[] args) {
//取得main主线程所在的线程组
ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
//为threadGroup指定父线程mainGroup,threadGroup线程组名字为A
ThreadGroup threadGroup = new ThreadGroup(mainGroup,"A");
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
System.out.println("runMethod!");
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
//将runnable和ThreadGroup通过Thread关联
Thread thread1 = new Thread(threadGroup, runnable);
thread1.setName("Z");
thread1.start();
//创建一个用于存放main线程组的子线程组的数组
ThreadGroup[] threadGroups = new ThreadGroup[Thread.currentThread().getThreadGroup().activeGroupCount()];
//将Thread.currentThread().getThreadGroup()复制到threadGroups
Thread.currentThread().getThreadGroup().enumerate(threadGroups);
//打印main线程组的子线程组数量以及第一个子线程组的名字
System.out.println("main线程中有多少个子线程组:"+threadGroups.length+" 名字为:"+threadGroups[0].getName());
//创建一个用于存放main线程组的第一个子线程组的线程的数组
Thread[] threads = new Thread[threadGroups[0].activeCount()];
//将 threadGroups[0]复制到threads
threadGroups[0].enumerate(threads);
//打印main线程组的第一个子线程组的第一个线程名
System.out.println(threads[0].getName());
}
}
即将main线程所在的线程组下创建一个新的线程组并取名为A,新建一个线程Z,将线程Z与A线程组关联。
创建一个用于存放main线程组的子线程组的数组threadGroups ,获取main线程组的子线程组的数组并将其复制到threadGroups,打印main线程组的子线程组数量以及第一个子线程组的名字。
创建一个用于存放main线程组的第一个子线程组的线程的数组threads,将 main线程组的第一个子线程组threadGroups[0]复制到threads,打印main线程组的第一个子线程组的第一个活动线程。
自动归属就是自动归到当前线程组中。
创建包threadgroup.test,在包下创建类Run3
package threadgroup.test;
public class Run3 {
public static void main(String[] args) {
//方法 activeGroupCount() 取得当前线程组对象中的子线程组数量
System.out.println("A处线程:"+Thread.currentThread().getName()
+" 所属线程组名为:" +Thread.currentThread().getThreadGroup().getName()
+" 中有线程组数量"+Thread.currentThread().getThreadGroup().activeGroupCount());
//自动加到main组中
ThreadGroup new_group = new ThreadGroup("new group");
ThreadGroup new_group2 = new ThreadGroup("new group2");
System.out.println("B处线程:"+Thread.currentThread().getName()+
" 中有线程组数量:"+Thread.currentThread().getThreadGroup().activeGroupCount());
//创建一个ThreadGroup数组用来存放当前线程组中的子线程组
ThreadGroup[] threadGroups = new ThreadGroup[Thread.currentThread().getThreadGroup().activeGroupCount()];
//方法enumerate() 作用是将线程组中的子线程组以复制的形式复制到ThreadGroup[]数组对象中
Thread.currentThread().getThreadGroup().enumerate(threadGroups);
for (int i = 0; i < threadGroups.length; i++) {
System.out.println("第"+(i+1)+"个线程组名称为:"+threadGroups[i].getName());
}
}
}
即在当前线程新建一个线程组后,该新建线程组自动归到当前线程组中,即成为当前线程组的子线程组。
获取父线程组
通过Thread.currentThread().getThreadGroup().getParent()获取父线程组, JVM的根线程组是System,再向上调用getParent()就会出现异常。
在线程组中加线程组
通过新建一个ThreadGroup(parent_threadGroup, “newGroup”),此时在parent_threadGroup线程组中添加一个子线程组newGroup。
批量停止组内的线程
使用线程组ThreadGroup的优点是可以批量处理本组内的线程对象。即可以调用线程组对象的方法。
递归取得与非递归取得组内对象
通过Thread.currentThread().getThreadGroup().enumerate(threadGroups1,true);这段代码实现,传入是true时,允许递归取得线程对象或线程组下的所有线程组或线程对象。传入false时,只允许取得当前线程组或线程对象(即不允许递归取得)。
public static int activeCount(),作用是返回当前线程所在的线程组中活动线程的数目。
代码:Thread.activeCount()
public static int enumerate(Thread tarray[]),作用是将当前线程所在的线程组及其子组中每一个活动线程复制到指定的数组中。
代码:Thread.enumerate(Thread tarray[])
创建包threadcharacter.service,在包下创建MyService类
package threadcharacter.service;
public class MyService{
//当前线程的独立变量printCountLocal
private ThreadLocal<Integer> printCountLocal = new ThreadLocal<>();
//当前的打印位置(位置相对应某个线程),即0对应线程1,1对应线程2,2对应线程3
private static int currentPrintPosition = 0;
//最终的打印位置
private static int finalPrintPosition = 0;
synchronized public void printMethod(String eachThreadPrintChar,Integer eachThreadPrintPosition)
{
//设置当前线程的独立变量printCountLocal的值为0
printCountLocal.set(0);
//当前线程的独立变量printCountLocal小于3时跳出循环
while(printCountLocal.get()<3)
{
//当前的打印位置值等于3时,将当前的打印位置值重置为0
if(currentPrintPosition==3)
{
currentPrintPosition=0;
}
//将传入的eachThreadPrintPosition-1后对3取余,即取得区间[0-2],如果没有对应上具体的线程,则进入等待状态
while((eachThreadPrintPosition-1)%3!=currentPrintPosition)
{
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//最终的打印位置值+1
finalPrintPosition++;
System.out.println(Thread.currentThread().getName()+" "
+eachThreadPrintChar+" "
+"currentPrintPosition="+currentPrintPosition
+" printCountLocal.get()="+(printCountLocal.get()+1)
+" finalPrintPosition="+finalPrintPosition);
//当前的打印位置值+1
currentPrintPosition++;
//当前线程的独立变量printCountLocal的值+1
printCountLocal.set(printCountLocal.get()+1);
this.notifyAll();
}
}
}
创建包threadcharacter.thread,在包下创建MyThread类
package threadcharacter.thread;
import threadcharacter.service.MyService;
public class MyThread extends Thread{
private MyService myService;
private String eachThreadPrintChar;
private Integer eachThreadPrintPosition;
public MyThread(MyService myService,String eachThreadPrintChar,Integer eachThreadPrintPosition)
{
this.myService = myService;
this.eachThreadPrintChar = eachThreadPrintChar;
this.eachThreadPrintPosition = eachThreadPrintPosition;
}
@Override
public void run() {
myService.printMethod(eachThreadPrintChar,eachThreadPrintPosition);
}
}
创建包threadcharacter.test,在包下创建Run类
package threadcharacter.test;
import threadcharacter.service.MyService;
import threadcharacter.thread.MyThread;
public class Run {
public static void main(String[] args) {
MyService myService = new MyService();
MyThread a = new MyThread(myService, "A", 4);
a.setName("线程1");
a.start();
MyThread b = new MyThread(myService, "B", 5);
b.setName("线程2");
b.start();
MyThread c = new MyThread(myService, "C", 6);
c.setName("线程3");
c.start();
}
}
即生成3个线程,run方法调用同步方法printMethod,如果线程B或者线程C先占有锁,则调用wait方法进入等待状态。
线程A调用printMethod方法并占有锁,然后当前打印位置currentPrintPosition+1,最终打印位置finalPrintPosition+1,本地线程独立变量位置printCountLocal+1,唤醒所有休眠线程;
然后线程A会进入休眠,线程B会拿到锁,然后当前打印位置currentPrintPosition+1,最终打印位置finalPrintPosition+1,本地线程独立变量位置printCountLocal+1,唤醒所有休眠线程;
然后线程B会进入休眠,线程C会拿到锁,然后当前打印位置currentPrintPosition+1,最终打印位置finalPrintPosition+1,本地线程独立变量位置printCountLocal+1,唤醒所有休眠线程。
因为线程A,线程B,线程C顺序执行了一次后,此时A、B、C各自的本地线程独立变量位置printCountLocal都为1,最终打印位置finalPrintPosition都为3,当前打印位置currentPrintPosition在等于2时,进行输出再+1为3,但是在线程A第二次执行时,会将当前打印位置currentPrintPosition重置为0。以此反复执行3次。
创建一个simpledateformat.tools包,在包下创建工具类DateTools
package simpledateformat.tools;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateTools {
//将字符串转换为日期格式
public static Date parse(String formatPattern,String dateString) throws ParseException {
return new SimpleDateFormat(formatPattern).parse(dateString);
}
//将日期转换成字符串
public static String format(String formatPattern,Date date) throws ParseException {
return new SimpleDateFormat(formatPattern).format(date).toString();
}
}
创建一个simpledateformat.thread包,在包下创建线程类MyThread
package simpledateformat.thread;
import simpledateformat.tools.DateTools;
import java.text.ParseException;
import java.util.Date;
public class MyThread extends Thread{
private String dateString;
public MyThread(String dateString)
{
this.dateString = dateString;
}
@Override
public void run() {
try {
Date date = DateTools.parse("yyyy-MM-dd",dateString);
String newDateString = DateTools.format("yyyy-MM-dd",date).toString();
if(!newDateString.equals(dateString))
{
System.out.println("ThreadName="+this.getName()+"报错了 日期字符串:"+dateString+"转换成的日期为:"+newDateString);
}
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
}
创建一个simpledateformat包,在包下创建类Run
package simpledateformat;
import simpledateformat.thread.MyThread;
import java.text.ParseException;
import java.text.SimpleDateFormat;
public class Run {
public static void main(String[] args) throws ParseException {
String[] dateStringArray = new String[]{"2000-01-01","2000-01-02","2000-01-03","2000-01-04","2000-01-05",
"2000-01-06","2000-01-07","2000-01-08","2000-01-09","2000-01-10"};
MyThread[] myThreadArray = new MyThread[10];
for (int i = 0; i < 10; i++) {
myThreadArray[i] = new MyThread(dateStringArray[i]);
}
for (int i = 0; i < 10; i++) {
myThreadArray[i].start();
}
}
}
即在多线程环境下,需要创建多个SimpleDateFormat类的实例。如果不使用DateTools工具类来实现转换(即不使用创建多个类SimpleDateFormat的实例),则会出现打印信息和报错。
或是使用另一种方式(ThreadLocal)实现工具类DateTools
package simpledateformat.tools;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateTools {
private static ThreadLocal<SimpleDateFormat> t1 = new ThreadLocal<SimpleDateFormat>();
public static SimpleDateFormat getSimpleDateFormat(String datePattern)
{
SimpleDateFormat sdf = null;
sdf=t1.get();
if(sdf == null)
{
sdf = new SimpleDateFormat(datePattern);
t1.set(sdf);
}
return sdf;
}
}
即使用ThreadLocal让每个线程都有独立的SimpleDateFormat类的实例。
当单线程中出现异常时,我们可在该线程run()方法的catch语句中进行处理。当有多个线程中出现异常时,就得在每个线程run()方法的catch语句中进行处理,这样会造成代码的严重冗余。可以使用setDefaultUncaughtExceptionHandler()和setUncaughtExceptionHandler()方法来集中处理线程的异常。
1.对指定线程对象进行异常处理的方法setUncaughtExceptionHandler()
创建包threadexception.thread,在包下创建线程类MyThread
package threadexception.thread;
public class MyThread extends Thread{
@Override
public void run() {
String username = null;
System.out.println(username.hashCode());
}
}
创建包threadexception,在包下创建类Run
package threadexception;
import threadexception.thread.MyThread;
public class Run {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.setName("线程t1");
//使用setUncaughtExceptionHandler()方法对指定的线程对象设置默认的异常处理器,对单个线程对象进行异常处理
myThread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("线程:"+t.getName()+" 出现了异常:");
e.printStackTrace();
}
});
myThread.start();
MyThread myThread2 = new MyThread();
myThread2.setName("线程t2");
myThread2.start();
}
}
2.对所有线程对象进行异常处理的方法setDefaultUncaughtExceptionHandler()
创建包threadexception.thread,在包下创建线程类MyThread
package threadexception.thread;
public class MyThread extends Thread{
@Override
public void run() {
String username = null;
System.out.println(username.hashCode());
}
}
创建包threadexception,在包下创建类Run2
package threadexception;
import threadexception.thread.MyThread;
public class Run2 {
public static void main(String[] args) {
//使用setDefaultUncaughtExceptionHandler()方法对所有线程对象设置默认的异常处理器
MyThread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("线程:"+t.getName()+" 出现了异常:");
e.printStackTrace();
}
});
MyThread myThread = new MyThread();
myThread.setName("线程t1");
myThread.start();
MyThread myThread2 = new MyThread();
myThread2.setName("线程t2");
myThread2.start();
}
}
实现线程组内一个线程出现异常报错,全部线程都停止。
创建包threadexception.threadgroup,在包下创建线程组类MyThreadGroup
package threadexception.threadgroup;
public class MyThreadGroup extends ThreadGroup{
public MyThreadGroup(String name) {
super(name);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
super.uncaughtException(t, e);
this.interrupt();
}
}
该uncaughtException()方法用以捕获该线程组中的错误,此时在捕获到错误后,直接将中断属性interrupt设置为true。
创建包threadexception.thread,在包下创建线程类MyThread2
package threadexception.thread;
public class MyThread2 extends Thread{
private String num;
public MyThread2(ThreadGroup group,String name,String num)
{
//调用Thread的构造方法,将父线程组和线程组名作为参数传入
super(group,name);
this.num = num;
}
@Override
public void run() {
int numInt = Integer.parseInt(num);
while(this.isInterrupted()==false)
System.out.println("死循环中:"+Thread.currentThread().getName());
}
}
创建包threadexception,在包下创建类Run3
package threadexception;
import threadexception.thread.MyThread2;
import threadexception.threadgroup.MyThreadGroup;
public class Run3 {
public static void main(String[] args) {
MyThreadGroup threadGroup = new MyThreadGroup("我的线程组");
MyThread2[] myThreads = new MyThread2[10];
for (int i = 0; i < myThreads.length; i++) {
MyThread2 myThread2 = new MyThread2(threadGroup, "线程" + (i + 1), "1");
myThread2.start();
}
MyThread2 newT = new MyThread2(threadGroup, "报错线程" , "a");
newT.start();
}
}
即实现在线程组内一个线程报错,线程组全部停止。通过10个正常的线程和一个会报错的线程来模拟,在MyThreadGroup中,通过uncaughtException()方法来捕获线程组内的错误,给中断属性interrupt赋值为true,然后此时线程组内的所有线程在检测到该值为true时,就会停止运行。
package threadexception.thread;
public class MyThread3 extends Thread{
private String num = "a";
public MyThread3()
{
super();
}
public MyThread3(ThreadGroup group,String name)
{
super(group,name);
}
@Override
public void run() {
super.run();
int numInt = Integer.parseInt(num);
System.out.println("在线程中打印:"+(numInt+1));
}
}
package threadexception.uncaughtexceptionhandler;
public class ObjectUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("对象的异常处理");
e.printStackTrace();
}
}
创建包threadexception.uncaughtexceptionhandler,在包下创建类StateUncaughtExceptionHandler
package threadexception.uncaughtexceptionhandler;
public class StateUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("静态的异常处理");
e.printStackTrace();
}
}
创建包threadexception.threadgroup,在包下创建类MyThreadGroup2
package threadexception.threadgroup;
public class MyThreadGroup2 extends ThreadGroup{
public MyThreadGroup2(String name) {
super(name);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
super.uncaughtException(t, e);
System.out.println("线程组的异常处理");
e.printStackTrace();
}
}
创建包threadexception,在包下创建类Run4
package threadexception;
import threadexception.thread.MyThread3;
import threadexception.threadgroup.MyThreadGroup2;
import threadexception.uncaughtexceptionhandler.ObjectUncaughtExceptionHandler;
import threadexception.uncaughtexceptionhandler.StateUncaughtExceptionHandler;
public class Run4 {
public static void main(String[] args) {
//创建线程组实例及线程
MyThreadGroup2 myThreadGroup2 = new MyThreadGroup2("线程组");
MyThread3 myThread3 = new MyThread3(myThreadGroup2,"线程");
//对象的异常捕获器
myThread3.setUncaughtExceptionHandler(new ObjectUncaughtExceptionHandler());
//类的异常捕获器(Default)
MyThread3.setDefaultUncaughtExceptionHandler(new StateUncaughtExceptionHandler());
myThread3.start();
/**
* 1.setUncaughtExceptionHandler的优先级比setDefaultUncaughtExceptionHandler和线程组的uncaughtException高。
* 2.要打印setDefaultUncaughtExceptionHandler内的处理器,
* 则需要在线程组uncaughtException方法上调用super.uncaughtException(t, e)。
*/
}
}
即创建三个可以捕获异常的类,StateUncaughtExceptionHandler、ObjectUncaughtExceptionHandler、MyThreadGroup2,通过MyThread3线程类绑定异常捕获器进行测试。