一、线程的状态

  线程对象在不同的运行时期有不同的状态,状态信息就存在于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)

Java多线程编程(七)线程状态、线程组与异常处理的更多相关文章

  1. java多线程编程(1) 线程的基本知识

    在前面研究过多线程与进程的区别. 这里在稍微总结一下: 进程:程序动态的一次执行过程. 线程:可以只是程序员的一部分的执行过程 每个进程有多个线程组成,在java程序中,至少两个线程一个是垃圾回收线程 ...

  2. Java多线程编程(三)线程间通信

    线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体.线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时 ...

  3. 使用Java 多线程编程 让三个线程轮流输出ABC,循环10次后结束

    简要分析: 要求三个线程轮流输出,这里我们要使用一个对象锁,让关键部分的代码放入同步块当中.同时要有一个变量记录打印的次数到达10次循环后不再打印,另外一个就是要给每个线程一个标志号,我们根据标识号来 ...

  4. Java多线程-两种常用的线程计数器CountDownLatch和循环屏障CyclicBarrier

    Java多线程编程-(1)-线程安全和锁Synchronized概念 Java多线程编程-(2)-可重入锁以及Synchronized的其他基本特性 Java多线程编程-(3)-从一个错误的双重校验锁 ...

  5. Java多线程编程核心(1)

    Java多线程编程核心(1) 停止线程 本节主要讨论如何更好停止一个线程.停止线程意味着在线程处理完成任务之前放弃当前操作. 1.停不了的线程 可能大多数同学会使用interrupt()来停止线程,但 ...

  6. Java多线程编程(4)--线程同步机制

    一.锁 1.锁的概念   线程安全问题的产生是因为多个线程并发访问共享数据造成的,如果能将多个线程对共享数据的并发访问改为串行访问,即一个共享数据同一时刻只能被一个线程访问,就可以避免线程安全问题.锁 ...

  7. “全栈2019”Java多线程第十三章:线程组ThreadGroup详解

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

  8. “全栈2019”Java多线程第七章:等待线程死亡join()方法详解

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

  9. 《java多线程编程核心技术》不使用等待通知机制 实现线程间通信的 疑问分析

    不使用等待通知机制 实现线程间通信的 疑问分析 2018年04月03日 17:15:08       ayf 阅读数:33 编辑 <java多线程编程核心技术>一书第三章开头,有如下案例: ...

  10. Java并发编程:进程和线程之由来

    Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够融会贯通 ...

随机推荐

  1. The type java.lang.AutoCloseable cannot be resolved. It is indirectly referenced from required .class files

    出现问题: The type java.lang.AutoCloseable cannot be resolved. It is indirectly referenced from required ...

  2. Scrapy项目 - 项目源码 - 实现腾讯网站社会招聘信息爬取的爬虫设计

    1.tencentSpider.py # -*- coding: utf-8 -*- import scrapy from Tencent.items import TencentItem #创建爬虫 ...

  3. SUSE Linux Enterprise 15 SP1 系统安装

    从 SUSE Linux Enterprise Server 15 开始,安装媒体仅包含安装程序 - 一个用于安装.更新和注册 SUSE Linux Enterprise Server 的基于命令行的 ...

  4. VS Code配置Go语言开发环境(建议使用goland)

    VS Code是微软开源的一款编辑器,插件系统十分的丰富.本文就介绍了如何使用VS Code搭建Go语言开发环境. VS Code配置Go语言开发环境 说在前面的话,Go语言是采用UTF8编码的,理论 ...

  5. Java 学习笔记之 Stop停止线程

    Stop停止线程: 使用stop()方法停止线程是非常暴力的,会抛出java.lang.ThreadDeath Error,但是我们无需显示捕捉, 以下捕捉只是为了看得更清晰. public clas ...

  6. js模拟下拉菜单-键盘、鼠标(代码详解)

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. java第3天:Scanner,Random,ArrayList

    第一章:Scanner从入门到放弃 1 API的概述和使用步骤 API简称应用程序编程接口,是JDK给我们提供好的可以直接使用的类和方法,是程序员随手使用的字典. *** 2 Scanner的概述 2 ...

  8. meta标签中设置以极速模式打开网页

    1.网页meta标签中X-UA-Compatible属性的使用的极速模式 <meta http-equiv="X-UA-Compatible" content="I ...

  9. JDK1.7中HashMap死环问题及JDK1.8中对HashMap的优化源码详解

    一.JDK1.7中HashMap扩容死锁问题 我们首先来看一下JDK1.7中put方法的源码 我们打开addEntry方法如下,它会判断数组当前容量是否已经超过的阈值,例如假设当前的数组容量是16,加 ...

  10. 重磅发布 | 全球首个云原生应用标准定义与架构模型 OAM 正式开源

    作者: OAM 项目负责人 导读:2019 年 10 月 17 日,阿里巴巴合伙人.阿里云智能基础产品事业部总经理蒋江伟(花名:小邪)在 Qcon 上海重磅宣布,阿里云与微软联合推出开放应用模型 Op ...