Java多线程编程(七)线程状态、线程组与异常处理

  一、线程的状态

  线程对象在不同的运行时期有不同的状态,状态信息就存在于State枚举类中。

  调用与线程有关的方法后,会进入不同的线程状态,这些状态之间某些是可双向切换的,比如WAITING和RUNNING状态之间可以循环地进行切换;而有些是单向切换的,比如线程销毁后并不能自动进入RUNNING状态。

  1.验证NEW、RUNNABLE和TERMINATED

  NEW状态时线程实例化后还从未执行start()方法时的状态,而RUNNABLE状态是线程进入运行的状态,TERMINATED是线程被销毁时的状态。

  示例:从输出结果可以看出,第一行打印main主线程的状态为RUNNABLE,然后t线程在还没start之前呈NEW状态,执行start后t线程是RUNNABLE状态,在线程执行完后t线程被销毁,进入TERMINATED状态。

package extthread;

public class MyThread extends Thread {

    public MyThread() {
        System.out.println("构造方法中的状态:" + Thread.currentThread().getState());
    }

    @Override
    public void run() {
        System.out.println("run方法中的状态:" + Thread.currentThread().getState());
    }

}
package test;

import extthread.MyThread;

public class Run {

    // NEW,
    // RUNNABLE,
    // TERMINATED,

    // BLOCKED,
    // WAITING,
    // TIMED_WAITING,

    public static void main(String[] args) {
        try {
            MyThread t = new MyThread();
            System.out.println("main方法中的状态1:" + t.getState());
            Thread.sleep(1000);
            t.start();
            Thread.sleep(1000);
            System.out.println("main方法中的状态2:" + t.getState());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}
构造方法中的状态:RUNNABLE
main方法中的状态1:NEW
run方法中的状态:RUNNABLE
main方法中的状态2:TERMINATED

  2.验证TIMED_WAITING

  线程状态TIMED_WAITING代表线程执行了Thread.sleep()方法,呈等待状态,等待时间到达,继续向下运行。

  示例:从输出结果可以看出,t线程执行了Thread.sleep()方法后,呈TIMED_WAITING状态。

package extthread;

public class MyThread extends Thread {

    @Override
    public void run() {
        try {
            System.out.println("begin sleep");
            Thread.sleep(10000);
            System.out.println("  end sleep");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
package test;

import extthread.MyThread;

public class Run {

    // NEW,
    // RUNNABLE,
    // TERMINATED,

    // BLOCKED,
    // WAITING,
    // TIMED_WAITING,

    public static void main(String[] args) {
        try {
            MyThread t = new MyThread();
            t.start();
            Thread.sleep(1000);
            System.out.println("main方法中的状态:" + t.getState());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}
begin sleep
main方法中的状态:TIMED_WAITING
  end sleep

  3.验证BLOCKED

  BLOCKED状态出现在某一个线程在等待锁的时候。

  示例:b线程等待a线程执行到执行完这段时间处于BLOCKED状态。

package service;

public class MyService {

    synchronized static public void serviceMethod() {
        try {
            System.out.println(Thread.currentThread().getName() + "进入了业务方法!");
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
package extthread;

import service.MyService;

public class MyThread1 extends Thread {

    @Override
    public void run() {
        MyService.serviceMethod();
    }

}
package extthread;

import service.MyService;

public class MyThread2 extends Thread {

    @Override
    public void run() {
        MyService.serviceMethod();
    }

}
package test;

import extthread.MyThread1;
import extthread.MyThread2;

public class Run {

    // NEW,
    // RUNNABLE,
    // TERMINATED,
    // BLOCKED,
    // WAITING,
    // TIMED_WAITING,

    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1();
        t1.setName("a");
        MyThread2 t2 = new MyThread2();
        t2.setName("b");
        
        t1.start();
        Thread.sleep(100);
        t2.start(); System.out.println(
"main方法中的t2状态:" + t2.getState()); } }
a进入了业务方法!
main方法中的t2状态:BLOCKED
b进入了业务方法!

  4.验证WAITING

  WAITING状态是线程执行了Object.wait()方法后所处的状态。

  示例:执行完Lock.lock.wait();后线程t进入了WAITING状态。

package service;

public class Lock {

    public static final Byte lock = new Byte("0");

}
package extthread;

import service.Lock;

public class MyThread extends Thread {

    @Override
    public void run() {
        try {
            synchronized (Lock.lock) {
                Lock.lock.wait();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
package test;

import extthread.MyThread;

public class Run {

    // NEW,
    // RUNNABLE,
    // TERMINATED,
    // BLOCKED,
    // WAITING,
    // TIMED_WAITING,

    public static void main(String[] args) {
        try {
            MyThread t = new MyThread();
            t.start();
            Thread.sleep(1000);
            System.out.println("main方法中的t状态:" + t.getState());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}
main方法中的t状态:WAITING

  二、线程组

  线程组的作用是,可以批量地管理线程或线程对象,有效地对线程或线程组对象进行组织。

  1.线程对象关联线程组:1级关联

  1级关联就是父对象中有子对象,但并不创建子孙对象。这种情况经常出现在开发中,比如创建一些线程时,为了有效地对这些线程进行组织管理,通常的情况下是创建一个线程组,然后再将部分线程归属到该组中。这样的处理可以对零散的线程对象进行有效地组织和规划。

  示例:Thread aThread = new Thread(group, aRunnable);Thread bThread = new Thread(group, bRunnable);将线程A和B加入到线程组中,然后分别start启动。

package extthread;

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) {
            e.printStackTrace();
        }
    }

}
package extthread;

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) {
            e.printStackTrace();
        }
    }

}
活动的线程数为:2
线程组的名称为:group线程组
ThreadName=Thread-2
ThreadName=Thread-3
ThreadName=Thread-2
ThreadName=Thread-3
ThreadName=Thread-3
ThreadName=Thread-2
...

  2.线程对象关联线程组:多级关联

  多级关联就是父对象中有子对象,子对象中再创建子对象,也就是出现子孙对象的效果了。但是,线程树结构设计得非常复杂反而不利于线程对象的管理。

  示例:首先在main组中添加了一个线程组A,然后在A组中添加了线程对象Z,也就是子孙对象的效果。

package test.run;

public class Run {

    public static void main(String[] args) {

        // 在main组中添加一个线程组A,然后在这个A组中添加线程对象Z
        ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
        ThreadGroup group = new ThreadGroup(mainGroup, "A");
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("runMethod!");
                    Thread.sleep(10000);//线程必须在运行状态才可以受组管理
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        Thread newThread = new Thread(group, runnable);
        newThread.setName("Z");
        newThread.start();// 线程必须启动后才归到A组中
        // ///
     //方法activeGroupCount()的作用是取得当前线程组对象中的子线程组数量
     //方法enumerate()的作用是将线程组中的子线程组以复制的形式拷贝到ThreadGroup[]数组对象中
ThreadGroup[] listGroup = new ThreadGroup[Thread.currentThread() .getThreadGroup().activeGroupCount()]; Thread.currentThread().getThreadGroup().enumerate(listGroup); System.out.println("main线程中子线程组个数:" + listGroup.length + " 名称为:" + listGroup[0].getName()); Thread[] listThread = new Thread[listGroup[0].activeCount()]; listGroup[0].enumerate(listThread); System.out.println(listThread[0].getName()); } }
runMethod!
main线程中子线程组个数:1 名称为:A
Z

  3.线程组自动归属特性

  自动归属就是自动归到当前线程中。

  示例:在实例化一个ThreadGroup线程组x时如果不指定所属的线程组,则x线程组自动归到当前线程对象所属的线程组中,也就是隐式地在一个线程组中添加了一个子线程组,所以在控制台中打印的线程组数量值由0变成了1。

package test.run;

public class Run {
    public static void main(String[] args) {
        System.out.println(" "+Thread.currentThread().getName()
                          +" "+Thread.currentThread().getThreadGroup().getName()
                          +" "+Thread.currentThread().getThreadGroup().activeGroupCount());
        ThreadGroup group=new ThreadGroup("新建线程组");
        System.out.println(" "+Thread.currentThread().getName()
                  +" "+Thread.currentThread().getThreadGroup().getName()
                  +" "+Thread.currentThread().getThreadGroup().activeGroupCount());
        ThreadGroup[] threadGroup=new ThreadGroup[Thread.currentThread().getThreadGroup().activeGroupCount()];
        Thread.currentThread().getThreadGroup().enumerate(threadGroup);
        for (int i = 0; i < threadGroup.length; i++) {
            System.out.println("第一个线程组名称为:"+threadGroup[i].getName());
        }
    }
}
 main main 0
 main main 1
第一个线程组名称为:新建线程组

  4.获取根线程组

  示例:main线程所在的线程组为main线程组,JVM的根线程组就是system,再去其父线程则出现了空指针异常。

package test.run;

public class Run {

    public static void main(String[] args) {
        System.out.println("确定当前线程是:" + Thread.currentThread().getName()
                         + " 所在线程组名称为:"+ Thread.currentThread().getThreadGroup().getName());
        System.out.println("main所在的线程组的父线程的名称为:"
                        + Thread.currentThread().getThreadGroup().getParent().getName());
        System.out.println("main所在的线程组的父线程的父线程的名称为:"
                + Thread.currentThread().getThreadGroup().getParent()
                        .getParent().getName());
    }

}
确定当前线程是:main 所在线程组名称为:main
main所在的线程组的父线程的名称为:system
Exception in thread "main" java.lang.NullPointerException
	at test.run.Run.main(Run.java:12)

  5.线程组里加线程组

  示例:使用ThreadGroup newGroup = new ThreadGroup(Thread.currentThread().getThreadGroup(), "newGroup");显示地在一个线程组中添加了一个子线程组。

package test.run;

public class Run {

    public static void main(String[] args) {

        System.out.println("1当前线程组名称: "+ Thread.currentThread().getThreadGroup().getName());
        System.out.println("2当前线程组中活动的线程数量: "+ Thread.currentThread().getThreadGroup().activeCount());
        System.out.println("3添加之前线程组中线程组的数量: "+ Thread.currentThread().getThreadGroup().activeGroupCount());
        ThreadGroup newGroup = new ThreadGroup(Thread.currentThread().getThreadGroup(), "newGroup");
        System.out.println("4添加之前线程组中线程组的数量: "+ Thread.currentThread().getThreadGroup().activeGroupCount());
        System.out.println("5父线程组名称: "+ Thread.currentThread().getThreadGroup().getParent().getName());
    }
1当前线程组名称: main
2当前线程组中活动的线程数量: 1
3添加之前线程组中线程组的数量: 0
4添加之前线程组中线程组的数量: 1
5父线程组名称: system

  6.组内的线程批量停止

  示例:调用group.interrupt();使5个线程全部都从无限死循环中中断,可以将正在运行的线程批量停止。

package mythread;

public class MyThread extends Thread {

    public MyThread(ThreadGroup group, String name) {
        super(group, name);
    }

    @Override
    public void run() {
        System.out.println("ThreadName=" + Thread.currentThread().getName()+ "进入循环前)");
        while (!this.isInterrupted()) {
            }
        System.out.println("ThreadName=" + Thread.currentThread().getName()+ "进入循环后)");
    }

}
package test.run;

import mythread.MyThread;

public class Run {

    public static void main(String[] args) {
        try {
            ThreadGroup group = new ThreadGroup("线程组");

            for (int i = 0; i < 5; i++) {
                MyThread thread = new MyThread(group, "线程" + (i + 1));
                thread.start();
            }
            Thread.sleep(5000);
            group.interrupt();
            System.out.println("调用了group.interrupt();");
        } catch (InterruptedException e) {
            System.out.println("进入catch代码块!");
            e.printStackTrace();
        }

    }

}
ThreadName=线程1进入循环前!
ThreadName=线程5进入循环前!
ThreadName=线程4进入循环前!
ThreadName=线程2进入循环前!
ThreadName=线程3进入循环前!
调用了group.interrupt();
ThreadName=线程4进入循环后!
ThreadName=线程3进入循环后!
ThreadName=线程2进入循环后!
ThreadName=线程1进入循环后!
ThreadName=线程5进入循环后!

  7.递归与非递归取得组内对象

  示例:Thread.currentThread().getThreadGroup().enumerate(listGroup1, true);线程A的参数设置为true是递归取得子组及子孙组,而Thread.currentThread().getThreadGroup().enumerate(listGroup2, false);线程B的参数设置为false是非递归取得线程组内的对象。

package test.run;

public class Run {

    public static void main(String[] args) {

        ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
        ThreadGroup groupA = new ThreadGroup(mainGroup, "A");
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("runMethod!");
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        ThreadGroup groupB = new ThreadGroup(groupA, "B");

        ThreadGroup[] listGroup1 = new ThreadGroup[Thread.currentThread().getThreadGroup().activeGroupCount()];
        Thread.currentThread().getThreadGroup().enumerate(listGroup1, true);
        for (int i = 0; i < listGroup1.length; i++) {
            if (listGroup1[i] != null) {
                System.out.println(listGroup1[i].getName());
            }
        }
        
        ThreadGroup[] listGroup2 = new ThreadGroup[Thread.currentThread().getThreadGroup().activeGroupCount()];
        Thread.currentThread().getThreadGroup().enumerate(listGroup2, false);
        for (int i = 0; i < listGroup2.length; i++) {
            if (listGroup2[i] != null) {
                System.out.println(listGroup2[i].getName());
            }
        }

    }

}
A
B
A

  三、使线程具有有序性

  正常的情况下,线程在运行时多个线程之间执行任务的时机是无序的,可以通过改造代码的方式使它们运行具有有序性。

  示例:通过给不同线程的showNumPosition赋予不同的值以及if (addNumber % 3 == showNumPosition) {...}判断语句来实现代码有序运行的效果。

package mythread;

public class MyThread extends Thread {

    private Object lock;
    private String showChar;
    private int showNumPosition;

    private int printCount = 0;//统计打印了几个字母

    volatile private static int addNumber = 1;

    public MyThread(Object lock, String showChar, int showNumPosition) {
        super();
        this.lock = lock;
        this.showChar = showChar;
        this.showNumPosition = showNumPosition;
    }

    @Override
    public void run() {
        try {
            synchronized (lock) {
                while (true) {
                    if (addNumber % 3 == showNumPosition) {
                        System.out.println("ThreadName="+ Thread.currentThread().getName()
                                         + " runCount=" + addNumber + " " + showChar);
                        lock.notifyAll();
                        addNumber++;
                        printCount++;
                        if (printCount == 3) {
                            break;
                        }
                    } else {
                        lock.wait();
                    }
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}
package mythread;

public class MyThread extends Thread {

    private Object lock;
    private String showChar;
    private int showNumPosition;

    private int printCount = 0;//统计打印了几个字母

    volatile private static int addNumber = 1;

    public MyThread(Object lock, String showChar, int showNumPosition) {
        super();
        this.lock = lock;
        this.showChar = showChar;
        this.showNumPosition = showNumPosition;
    }

    @Override
    public void run() {
        try {
            synchronized (lock) {
                while (true) {
                    if (addNumber % 3 == showNumPosition) {
                        System.out.println("ThreadName="+ Thread.currentThread().getName()
                                         + " runCount=" + addNumber + " " + showChar);
                        lock.notifyAll();
                        addNumber++;
                        printCount++;
                        if (printCount == 3) {
                            break;
                        }
                    } else {
                        lock.wait();
                    }
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}
ThreadName=Thread-0 runCount=1 A
ThreadName=Thread-1 runCount=2 B
ThreadName=Thread-2 runCount=3 C
ThreadName=Thread-0 runCount=4 A
ThreadName=Thread-1 runCount=5 B
ThreadName=Thread-2 runCount=6 C
ThreadName=Thread-0 runCount=7 A
ThreadName=Thread-1 runCount=8 B
ThreadName=Thread-2 runCount=9 C

  四、SimpleDateFormat非线程安全

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

  1.出现异常

  示例:使用单例的SimpleDateFormat类在多线程的环境中处理日期,极易出现日期转换错误的情况。

package extthread;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MyThread extends Thread {

    private SimpleDateFormat sdf;
    private String dateString;

    public MyThread(SimpleDateFormat sdf, String dateString) {
        super();
        this.sdf = sdf;
        this.dateString = dateString;
    }

    @Override
    public void run() {
        try {
            Date dateRef = sdf.parse(dateString);
            String newDateString = sdf.format(dateRef).toString();
            if (!newDateString.equals(dateString)) {
                System.out.println("ThreadName=" + this.getName()
                        + "日期字符串为:" + dateString + " 转换成的日期为:"
                        + newDateString);
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }

    }

}
package test.run;

import java.text.SimpleDateFormat;

import extthread.MyThread;

public class Test {

    public static void main(String[] args) {

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

        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[] threadArray = new MyThread[10];
        for (int i = 0; i < 10; i++) {
            threadArray[i] = new MyThread(sdf, dateStringArray[i]);
        }
        for (int i = 0; i < 10; i++) {
            threadArray[i].start();
        }

    }
}
ThreadName=Thread-5日期字符串为:2000-01-06 转换成的日期为:2000-01-04
Exception in thread "Thread-1" ThreadName=Thread-2日期字符串为:2000-01-03 转换成的日期为:2200-01-03
ThreadName=Thread-4日期字符串为:2000-01-05 转换成的日期为:2000-01-04
ThreadName=Thread-0日期字符串为:2000-01-01 转换成的日期为:2200-01-01
java.lang.NumberFormatException: empty String
	at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source)
	at sun.misc.FloatingDecimal.parseDouble(Unknown Source)
	at java.lang.Double.parseDouble(Unknown Source)
	at java.text.DigitList.getDouble(Unknown Source)
	at java.text.DecimalFormat.parse(Unknown Source)
	at java.text.SimpleDateFormat.subParse(Unknown Source)
	at java.text.SimpleDateFormat.parse(Unknown Source)
	at java.text.DateFormat.parse(Unknown Source)
	at extthread.MyThread.run(MyThread.java:21)
ThreadName=Thread-7日期字符串为:2000-01-08 转换成的日期为:2000-01-09

  2.解决异常方法1

  示例:可以通过创建多个SimpleDateFormat类的实例来解决异常问题,由于 if (!newDateString.equals(dateString)) 判断语句判断为假,所以不会执行输出语句,控制台没有输出,原因就是每个线程单独处理自己的SimpleDateFormat类的实例,互相互不干扰。

package 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) {
        return new SimpleDateFormat(formatPattern).format(date).toString();
    }

}
package extthread;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import tools.DateTools;

public class MyThread extends Thread {

    private SimpleDateFormat sdf;
    private String dateString;

    public MyThread(SimpleDateFormat sdf, String dateString) {
        super();
        this.sdf = sdf;
        this.dateString = dateString;
    }

    @Override
    public void run() {
        try {
            Date dateRef = DateTools.parse("yyyy-MM-dd", dateString);
            String newDateString = DateTools.format("yyyy-MM-dd", dateRef)
                    .toString();
            if (!newDateString.equals(dateString)) {
                System.out.println("ThreadName=" + this.getName()
                        + "日子字符串:" + dateString + "转换成的日期为:"
                        + newDateString);
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }

    }

}
package test.run;

import java.text.SimpleDateFormat;

import extthread.MyThread;

public class Test {

    public static void main(String[] args) {

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

        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[] threadArray = new MyThread[10];
        for (int i = 0; i < 10; i++) {
            threadArray[i] = new MyThread(sdf, dateStringArray[i]);
        }
        for (int i = 0; i < 10; i++) {
            threadArray[i].start();
        }

    }
}

  3.解决异常方法2

  示例:通过使用ThreadLocal类能使线程绑定到指定的对象的特性,也可以解决多线程环境下SimpleDateFormat类处理错误的情况。使用tl.set(sdf);代码使线程和SimpleDateFormat类的实例对象绑定,也可以达到线程之间互相处理不受干扰的效果。

package tools;

import java.text.SimpleDateFormat;

public class DateTools {

    private static ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>();

    public static SimpleDateFormat getSimpleDateFormat(String datePattern) {
        SimpleDateFormat sdf = null;
        sdf = tl.get();
        if (sdf == null) {
            sdf = new SimpleDateFormat(datePattern);
            tl.set(sdf);
        }
        return sdf;
    }

}
package extthread;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import tools.DateTools;

public class MyThread extends Thread {

    private SimpleDateFormat sdf;
    private String dateString;

    public MyThread(SimpleDateFormat sdf, String dateString) {
        super();
        this.sdf = sdf;
        this.dateString = dateString;
    }

    @Override
    public void run() {
        try {
            Date dateRef = DateTools.getSimpleDateFormat("yyyy-MM-dd").parse(
                    dateString);
            String newDateString = DateTools.getSimpleDateFormat("yyyy-MM-dd")
                    .format(dateRef).toString();
            if (!newDateString.equals(dateString)) {
                System.out.println("ThreadName=" + this.getName()
                        + "日期字符串:" + dateString + " 转换之后的日期:"
                        + newDateString);
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }

    }

}

  五、线程中出现异常的处理

package extthread;

public class MyThread extends Thread {
    @Override
    public void run() {
        String username = null;
        System.out.println(username.hashCode());
    }

}

  示例1:由于username字段为null,所以运行后输出了空指针异常。

package controller;

import extthread.MyThread;

public class Main1 {

    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();
    }

}
Exception in thread "Thread-0" java.lang.NullPointerException
	at extthread.MyThread.run(MyThread.java:7)

   示例2:在Java的多线程技术中,可以使用UncaughtExceptionHandler类对多线程中的异常进行“捕捉”,t1.setUncaughtExceptionHandler(new UncaughtExceptionHandler(){}的作用是对t1线程对象设置默认的异常处理器。

package controller;

import java.lang.Thread.UncaughtExceptionHandler;

import extthread.MyThread;

public class Main2 {

    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.setName("线程t1");
        t1.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println("线程:" + t.getName() + " 出现了异常");
                e.printStackTrace();
            }
        });
        t1.start();

        MyThread t2 = new MyThread();
        t2.setName("线程t2");
        t2.start();
    }
}
Exception in thread "线程t2" 线程:线程t1 出现了异常
java.lang.NullPointerException
	at extthread.MyThread.run(MyThread.java:7)
java.lang.NullPointerException
	at extthread.MyThread.run(MyThread.java:7)

   示例3:还可以使用MyThread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {}对所有线程对象设置异常处理器。

package controller;

import java.lang.Thread.UncaughtExceptionHandler;

import extthread.MyThread;

public class Main3 {

    public static void main(String[] args) {
        MyThread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
                    @Override
                    public void uncaughtException(Thread t, Throwable e) {
                        System.out.println("线程:" + t.getName() + " 出现了异常!");
                        e.printStackTrace();

                    }
                });

        MyThread t1 = new MyThread();
        t1.setName("线程t1");
        t1.start();

        MyThread t2 = new MyThread();
        t2.setName("线程t2");
        t2.start();
    }
}
线程:线程t1 出现了异常!
线程:线程t2 出现了异常!
java.lang.NullPointerException
	at extthread.MyThread.run(MyThread.java:7)
java.lang.NullPointerException
	at extthread.MyThread.run(MyThread.java:7)

  六、线程组内处理异常

  示例1:程序运行后,其中一个线程出现异常,但是其他线程却一直以死循环的方式持续打印结果。默认情况下,线程组中一个线程出现异常不会影响到其他线程的运行。

package extthread;

public class MyThread extends Thread {

    private String num;

    public MyThread(ThreadGroup group, String name, String num) {
        super(group, name);
        this.num = num;
    }

    @Override
    public void run() {
        int numInt = Integer.parseInt(num);
        while (true) {
            System.out.println("死循环中:" + Thread.currentThread().getName());
        }

    }

}
package test.run;

import extthread.MyThread;

public class Run {

    public static void main(String[] args) {
        ThreadGroup group = new ThreadGroup("我的线程组");
        MyThread[] myThread = new MyThread[10];
        for (int i = 0; i < myThread.length; i++) {
            myThread[i] = new MyThread(group, "线程" + (i + 1), "1");
            myThread[i].start();
        }
        MyThread newT = new MyThread(group, "报错线程", "a");
        newT.start();
    }

}
死循环中:线程6Exception in thread "报错线程" java.lang.NumberFormatException: For input string: "a"
        at java.lang.NumberFormatException.forInputString(Unknown Source)死循环中:线程9
        at java.lang.Integer.parseInt(Unknown Source)
    
        ...

   示例2:要想实现线程组内一个线程出现异常后全部线程都停止运行的话,使用public void uncaughtException(Thread t, Throwable e) {...}方法,其中t参数是出现异常的线程对象,这种情况下需要注意的就是,每个线程的run(0方法内部不要有异常catch语句,如果有catch语句,那么uncaughtException方法就不会执行。通过输出也可以看出,其中一个线程出现了异常,其他线程全部停止了。

package extthreadgroup;

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();
    }

}
package extthread;

public class MyThread extends Thread {

    private String num;

    public MyThread(ThreadGroup group, String name, String num) {
        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());
        }
    }

}
package test.run;

import extthread.MyThread;
import extthreadgroup.MyThreadGroup;

public class Run {

    public static void main(String[] args) {
        MyThreadGroup group = new MyThreadGroup("我的线程组");
        MyThread[] myThread = new MyThread[10];
        for (int i = 0; i < myThread.length; i++) {
            myThread[i] = new MyThread(group, "线程" + (i + 1), "1");
            myThread[i].start();
        }
        MyThread newT = new MyThread(group, "报错线程", "a");
        newT.start();
    }

}
...
死循环中:线程3
死循环中:线程3
死循环中:线程3
	at java.lang.Integer.parseInt(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
死循环中:线程3	at extthread.MyThread.run(MyThread.java:14)

死循环中:线程3
死循环中:线程3
死循环中:线程3
死循环中:线程3
死循环中:线程3
死循环中:线程3
死循环中:线程3
死循环中:线程7
死循环中:线程6
死循环中:线程9
死循环中:线程2
死循环中:线程10
死循环中:线程4
死循环中:线程1
死循环中:线程8
死循环中:线程5

  七、线程异常处理的传递

  这么多异常处理的方式,如果放在一起运行,出现的结果会不一样。

package extthread;

public class MyThread extends Thread {

    private String num = "a";

    public MyThread() {
        super();
    }

    public MyThread(ThreadGroup group, String name) {
        super(group, name);
    }

    @Override
    public void run() {
        int numInt = Integer.parseInt(num);
        System.out.println("在线程中打印:" + (numInt + 1));
    }

}
package extthreadgroup;

public class MyThreadGroup extends ThreadGroup {

    public MyThreadGroup(String name) {
        super(name);
    }

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        super.uncaughtException(t, e);
        System.out.println("线程组的异常处理");
        e.printStackTrace();
    }

}
package test.extUncaughtExceptionHandler;

import java.lang.Thread.UncaughtExceptionHandler;

public class ObjectUncaughtExceptionHandler implements UncaughtExceptionHandler {

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println("对象的异常处理");
        e.printStackTrace();
    }

}
package test.extUncaughtExceptionHandler;

import java.lang.Thread.UncaughtExceptionHandler;

public class StateUncaughtExceptionHandler implements UncaughtExceptionHandler {

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println("静态的异常处理");
        e.printStackTrace();
    }

}

  示例1:这种情况下,对象的异常处理被运行。

package test;

import test.extUncaughtExceptionHandler.ObjectUncaughtExceptionHandler;
import test.extUncaughtExceptionHandler.StateUncaughtExceptionHandler;
import extthread.MyThread;

public class Run1 {

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        
        // 对象
        myThread.setUncaughtExceptionHandler(new ObjectUncaughtExceptionHandler());
        
        //
        MyThread.setDefaultUncaughtExceptionHandler(new StateUncaughtExceptionHandler());
        
        myThread.start();
    }
}
对象的异常处理
java.lang.NumberFormatException: For input string: "a"
	at java.lang.NumberFormatException.forInputString(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at extthread.MyThread.run(MyThread.java:17)

   示例2:对对象添加注释后,则是静态的异常处理被执行。

package test;

import test.extUncaughtExceptionHandler.ObjectUncaughtExceptionHandler;
import test.extUncaughtExceptionHandler.StateUncaughtExceptionHandler;
import extthread.MyThread;

public class Run1 {

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        
        // 对象
//        myThread.setUncaughtExceptionHandler(new ObjectUncaughtExceptionHandler());
        
        //
        MyThread.setDefaultUncaughtExceptionHandler(new StateUncaughtExceptionHandler());
        
        myThread.start();
    }
}
静态的异常处理
java.lang.NumberFormatException: For input string: "a"
	at java.lang.NumberFormatException.forInputString(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at extthread.MyThread.run(MyThread.java:17)

   示例3:这种情况下,对象的异常处理被运行。

package test;

import test.extUncaughtExceptionHandler.ObjectUncaughtExceptionHandler;
import test.extUncaughtExceptionHandler.StateUncaughtExceptionHandler;
import extthread.MyThread;
import extthreadgroup.MyThreadGroup;

public class Run2 {

    public static void main(String[] args) {
        MyThreadGroup group = new MyThreadGroup("我的线程组");
        MyThread myThread = new MyThread(group, "我的线程");
        // 对象
        myThread.setUncaughtExceptionHandler(new ObjectUncaughtExceptionHandler());
        
        //
        MyThread.setDefaultUncaughtExceptionHandler(new StateUncaughtExceptionHandler());
        myThread.start();

    }
}
对象的异常处理
java.lang.NumberFormatException: For input string: "a"
	at java.lang.NumberFormatException.forInputString(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at extthread.MyThread.run(MyThread.java:17)

   示例4:这种情况下,静态的异常处理和线程组的异常处理都被执行。(打印顺序可能不一样。)其中要想打印“静态的异常处理”具体信息,必须在public void uncaughtException(Thread t, Throwable e) {...}方法中加上super.uncaughtException(t, e);代码才行。

package test;

import test.extUncaughtExceptionHandler.ObjectUncaughtExceptionHandler;
import test.extUncaughtExceptionHandler.StateUncaughtExceptionHandler;
import extthread.MyThread;
import extthreadgroup.MyThreadGroup;

public class Run2 {

    public static void main(String[] args) {
        MyThreadGroup group = new MyThreadGroup("我的线程组");
        MyThread myThread = new MyThread(group, "我的线程");
        // 对象
//        myThread.setUncaughtExceptionHandler(new ObjectUncaughtExceptionHandler());
        
        //
        MyThread.setDefaultUncaughtExceptionHandler(new StateUncaughtExceptionHandler());
        myThread.start();

    }
}
静态的异常处理
线程组的异常处理
java.lang.NumberFormatException: For input string: "a"
	at java.lang.NumberFormatException.forInputString(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at extthread.MyThread.run(MyThread.java:17)
java.lang.NumberFormatException: For input string: "a"
	at java.lang.NumberFormatException.forInputString(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at extthread.MyThread.run(MyThread.java:17)

   示例5:两部分都注释后,这种情况下,线程组的异常处理被执行。

package test;

import test.extUncaughtExceptionHandler.ObjectUncaughtExceptionHandler;
import test.extUncaughtExceptionHandler.StateUncaughtExceptionHandler;
import extthread.MyThread;
import extthreadgroup.MyThreadGroup;

public class Run2 {

    public static void main(String[] args) {
        MyThreadGroup group = new MyThreadGroup("我的线程组");
        MyThread myThread = new MyThread(group, "我的线程");
        // 对象
//        myThread.setUncaughtExceptionHandler(new ObjectUncaughtExceptionHandler());
        
        ////        MyThread.setDefaultUncaughtExceptionHandler(new StateUncaughtExceptionHandler());
        myThread.start();

    }
}
Exception in thread "我的线程" java.lang.NumberFormatException: For input string: "a"
	at java.lang.NumberFormatException.forInputString(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at extthread.MyThread.run(MyThread.java:17)
线程组的异常处理
java.lang.NumberFormatException: For input string: "a"
	at java.lang.NumberFormatException.forInputString(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at java.lang.Integer.parseInt(Unknown Source)
	at extthread.MyThread.run(MyThread.java:17)
原文地址:https://www.cnblogs.com/BigJunOba/p/8991397.html