Java多執行緒學習筆記22之增補拾遺
詳細程式碼見:github程式碼地址
第七章
本章內容:
1. 執行緒組的使用
2. 如何切換執行緒的狀態
3. SimpleDateFormat類與多執行緒的解決方案
4. 如何處理執行緒的異常
執行緒的狀態
執行緒物件在不同的執行時期有不同的狀態,狀態資訊就存在於State列舉類中。
我們看看Thread的類中的列舉類State程式碼:
/** * A thread state. A thread can be in one of the following states: * <ul> * <li>{@link #NEW}<br> * A thread that has not yet started is in this state. * </li> * <li>{@link #RUNNABLE}<br> * A thread executing in the Java virtual machine is in this state. * </li> * <li>{@link #BLOCKED}<br> * A thread that is blocked waiting for a monitor lock * is in this state. * </li> * <li>{@link #WAITING}<br> * A thread that is waiting indefinitely for another thread to * perform a particular action is in this state. * </li> * <li>{@link #TIMED_WAITING}<br> * A thread that is waiting for another thread to perform an action * for up to a specified waiting time is in this state. * </li> * <li>{@link #TERMINATED}<br> * A thread that has exited is in this state. * </li> * </ul> * * <p> * A thread can be in only one state at a given point in time. * These states are virtual machine states which do not reflect * any operating system thread states. * * @since 1.5 * @see #getState */ public enum State { /** * Thread state for a thread which has not yet started. */ NEW, /** * Thread state for a runnable thread. A thread in the runnable * state is executing in the Java virtual machine but it may * be waiting for other resources from the operating system * such as processor. */ RUNNABLE, /** * Thread state for a thread blocked waiting for a monitor lock. * A thread in the blocked state is waiting for a monitor lock * to enter a synchronized block/method or * reenter a synchronized block/method after calling * {@link Object#wait() Object.wait}. */ BLOCKED, /** * Thread state for a waiting thread. * A thread is in the waiting state due to calling one of the * following methods: * <ul> * <li>{@link Object#wait() Object.wait} with no timeout</li> * <li>{@link #join() Thread.join} with no timeout</li> * <li>{@link LockSupport#park() LockSupport.park}</li> * </ul> * * <p>A thread in the waiting state is waiting for another thread to * perform a particular action. * * For example, a thread that has called <tt>Object.wait()</tt> * on an object is waiting for another thread to call * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on * that object. A thread that has called <tt>Thread.join()</tt> * is waiting for a specified thread to terminate. */ WAITING, /** * Thread state for a waiting thread with a specified waiting time. * A thread is in the timed waiting state due to calling one of * the following methods with a specified positive waiting time: * <ul> * <li>{@link #sleep Thread.sleep}</li> * <li>{@link Object#wait(long) Object.wait} with timeout</li> * <li>{@link #join(long) Thread.join} with timeout</li> * <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li> * <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li> * </ul> */ TIMED_WAITING, /** * Thread state for a terminated thread. * The thread has completed execution. */ TERMINATED; }
執行緒可以處於以上6種狀態:
(1) NEW
至今尚未啟動的執行緒處於這種狀態
(2) RUNNABLE
正在Java虛擬機器中執行的執行緒處於這種狀態,這種狀態下可以細分為Ready和
Running狀態,這裡有執行緒排程器的演算法來決定
(3) BLOCKED
受阻塞並等待某個監視器的執行緒處於這種狀態
(4) WAITING
無限期地等待另一個執行緒來執行某一特定操作的執行緒處於這種狀態
(5) TIME WAITING
等待另一執行緒來執行取決於指定等待時間的操作的執行緒處於這種狀態
(6) TERMINATED
已退出的執行緒處於這種狀態
下面是呼叫了對應方法後執行緒的生命週期的轉變示意圖:
從圖可以得知,在呼叫與執行緒有關的方法後,會進入不同的執行緒狀態,這些狀態之間某些是
可雙向切換的,比如WAITING和RUNNING狀態之間可以迴圈地進行切換;而有些是單項切換的
,比如執行緒銷燬後並不能自動進入RUNNING狀態
1. 執行緒的六種狀態
(1).驗證NEW、RUNNABLE和TERMINATED
瞭解執行緒的狀態有助於程式設計師監控執行緒物件所處的情況,比如哪些執行緒從未啟動,哪些執行緒
正在執行,哪些執行緒正在阻塞,哪些執行緒正在等待,哪些執行緒已經銷燬了。這些是與執行緒生命
週期相關的資訊。
NEW狀態是執行緒例項化後還未執行start()方法時的狀態,而RUNNABLE狀態是執行緒進入執行的
狀態,TERMINATED是執行緒被銷燬時的狀態。
package chapter07.section01.thread_7_1_1.project_1_stateTest1;
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 chapter07.section01.thread_7_1_1.project_1_stateTest1;
public class Run {
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) {
// TODO: handle exception
e.printStackTrace();
}
}
}
/*
result:
構造方法中的狀態: RUNNABLE 這個是main執行緒的狀態
main方法中的狀態1: NEW
run方法中的狀態: RUNNABLE
main方法中的狀態2: TERMINATED
*/
(2) 驗證TIMED_WAITING
執行緒狀態TIMED_WAITING代表執行緒執行了Thread.sleep()方法,呈等待狀態,等待時間到
達,繼續向下執行
package chapter07.section01.thread_7_1_2.project_1_stateTest2;
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) {
// TODO: handle exception
e.printStackTrace();
}
}
}
package chapter07.section01.thread_7_1_2.project_1_stateTest2;
public class Run {
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) {
// TODO: handle exception
e.printStackTrace();
}
}
}
/*
result:
begin sleep
main方法中的狀態: TIMED_WAITING
end sleep
*/
(3) 驗證BLOCKED
BLOCKED狀態出現在某一個執行緒在等待鎖的時候
package chapter07.section01.thread_7_1_3.project_1_stateTest3;
public class MyService {
synchronized static public void serviceMethod() {
try {
System.out.println(Thread.currentThread().getName() + "進入了業務方法!");
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
package chapter07.section01.thread_7_1_3.project_1_stateTest3;
public class MyThread1 extends Thread{
@Override
public void run() {
MyService.serviceMethod();
}
}
package chapter07.section01.thread_7_1_3.project_1_stateTest3;
public class MyThread2 extends Thread {
@Override
public void run() {
MyService.serviceMethod();
}
}
package chapter07.section01.thread_7_1_3.project_1_stateTest3;
public class Run {
public static void main(String[] args) throws InterruptedException{
MyThread1 t1 = new MyThread1();
t1.setName("a");
t1.start();
MyThread2 t2 = new MyThread2();
t2.setName("b");
t2.start();
Thread.sleep(1000);
System.out.println("main方法中的t2狀態: " + t2.getState());
}
}
/*
result:
a進入了業務方法!
main方法中的t2狀態: BLOCKED
b進入了業務方法!
*/
t2執行緒一直在等待t1釋放鎖,所以t2當時的狀態就是BLOCKED
(4) 驗證WAITING
狀態WAITING是執行緒執行了Object.wait()方法後所處的狀態
package chapter07.section01.thread_7_1_4.project_1_stateTest4;
public class Lock {
@SuppressWarnings("deprecation")
public static final Byte lock = new Byte("0");
}
package chapter07.section01.thread_7_1_4.project_1_stateTest4;
public class MyThread extends Thread {
@Override
public void run() {
try {
synchronized(Lock.lock) {
Lock.lock.wait();
}
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
package chapter07.section01.thread_7_1_4.project_1_stateTest4;
public class Run {
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) {
// TODO: handle exception
e.printStackTrace();
}
}
}
/*
result:
main方法中的狀態: WAITING
*/
2.執行緒組
可以把執行緒歸屬到某一個執行緒組中,執行緒組中可以有執行緒物件,也可以有執行緒組,組
中也還可以有執行緒,。這樣的組織結構有些類似於樹的形式。
執行緒組的作用是,可以批量的管理執行緒或執行緒組物件,有效地對執行緒或執行緒組物件進行組織。
(1) ThreadGroup原始碼分析
我們簡單來看一下ThreadGroup類部分程式碼:
public
class ThreadGroup implements Thread.UncaughtExceptionHandler {
private final ThreadGroup parent; 它的parent,是個私有常量
String name; //當前執行緒組的名字
int maxPriority; //組中執行緒的最大優先順序
boolean destroyed; //是否存活
boolean daemon; //是否守護
boolean vmAllowSuspension;
int nUnstartedThreads = 0;
int nthreads;
Thread threads[]; //記錄執行緒組中的執行緒
int ngroups;
ThreadGroup groups[]; //記錄執行緒組中的執行緒組
/**
* Creates an empty Thread group that is not in any Thread group.
* This method is used to create the system Thread group.
*/
//可以看到這個方法是c程式碼來呼叫,是私有構造器
private ThreadGroup() { // called from C code
this.name = "system"; //可以看到根組是system系統執行緒組
this.maxPriority = Thread.MAX_PRIORITY; //優先順序是最大的10
this.parent = null; //沒有父結點
}
/**
* Constructs a new thread group. The parent of this new group is
* the thread group of the currently running thread.
* <p>
* The <code>checkAccess</code> method of the parent thread group is
* called with no arguments; this may result in a security exception.
*
* @param name the name of the new thread group.
* @exception SecurityException if the current thread cannot create a
* thread in the specified thread group.
* @see java.lang.ThreadGroup#checkAccess()
* @since JDK1.0
*/
//我們就是通過這個有參構造器建立執行緒組的,呼叫這個方法的當前執行緒的所屬執行緒組也是
//建立的執行緒組的父執行緒組
public ThreadGroup(String name) {
this(Thread.currentThread().getThreadGroup(), name);
}
/**
* Creates a new thread group. The parent of this new group is the
* specified thread group.
* <p>
* The <code>checkAccess</code> method of the parent thread group is
* called with no arguments; this may result in a security exception.
*
* @param parent the parent thread group.
* @param name the name of the new thread group.
* @exception NullPointerException if the thread group argument is
* <code>null</code>.
* @exception SecurityException if the current thread cannot create a
* thread in the specified thread group.
* @see java.lang.SecurityException
* @see java.lang.ThreadGroup#checkAccess()
* @since JDK1.0
*/
//可以指定所屬執行緒組
public ThreadGroup(ThreadGroup parent, String name) {
this(checkParentAccess(parent), parent, name);
}
private ThreadGroup(Void unused, ThreadGroup parent, String name) {
this.name = name;
this.maxPriority = parent.maxPriority;
this.daemon = parent.daemon;
this.vmAllowSuspension = parent.vmAllowSuspension;
this.parent = parent;
parent.add(this);
}
(2) 執行緒物件關聯執行緒組: 1級關聯
1級關聯就是父物件中有子物件,但並不建立子孫物件。
舉例:
package chapter07.section02.thread_7_2_1.project_1_groupAddThread;
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) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
package chapter07.section02.thread_7_2_1.project_1_groupAddThread;
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) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
package chapter07.section02.thread_7_2_1.project_1_groupAddThread;
public class Run {
public static void main(String[] args) {
ThreadA aRunnable = new ThreadA();
ThreadB bRunnable = new ThreadB();
ThreadGroup group = new ThreadGroup("Clarence");
Thread aThread = new Thread(group, aRunnable);
Thread bThread = new Thread(group, bRunnable);
aThread.start();
bThread.start();
System.out.println("活動的執行緒數為:" + group.activeCount());
System.out.println("執行緒組的名稱為:" + group.getName());
}
}
/*
result:
活動的執行緒數為:2
執行緒組的名稱為:Clarence
ThreadName=Thread-3
ThreadName=Thread-2
...................
*/
(3) 執行緒物件關聯執行緒組: 多級關聯
此種寫法在開發中不太常見。如果執行緒樹結構設計得非常複雜反而不利於執行緒物件的管理
,但JDK卻提供了支援多級關聯的執行緒樹結構。
package chapter07.section02.thread_7_2_2.project_1_groupAddThreadMoreLevel;
public class Run {
public static void main(String[] args) {
// 在main組中新增一個執行緒組A,然後在這個A組中新增執行緒物件Z
// 方法activeGroupCount()和activeCount()的值不是固定的
// 是系統中環境的一個快照
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(1000); //執行緒必須在執行狀態才可以受組管理
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
}
};
Thread newThread = new Thread(group, runnable);
newThread.setName("z");
newThread.start(); // 執行緒必須啟動然後才歸到組A中
ThreadGroup listGroup[] = new ThreadGroup[Thread.currentThread()
.getThreadGroup().activeGroupCount()];
//java.lang.ThreadGroup.enumerate(ThreadGroup[] list):
//複製到該執行緒組中指定的陣列引用的所有活動子組
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());
}
}
/*
result:
main執行緒中有多少個子執行緒組: 1 名字為: A
runMethod!
z
*/
本實驗在main組建立了一個新組,在新組中添加了執行緒
(4) 執行緒自動歸屬特性
自動歸屬就是自動歸到當前執行緒組中
package chapter07.section02.thread_7_2_3.project_1_autoAddGroup;
public class Run {
public static void main(String[] args) {
System.out.println("A處執行緒:"+Thread.currentThread().getName()
+ " 所屬的執行緒組名為:"
+ Thread.currentThread().getThreadGroup().getName()+ " "
+ " 中有執行緒組數量:"+Thread.currentThread().getThreadGroup().activeGroupCount());
ThreadGroup group=new ThreadGroup("新的組"); //自動歸屬到當前執行緒的所屬組,即main組
System.out.println("B處執行緒:"+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());
}
}
}
/*
result:
A處執行緒:main 所屬的執行緒組名為:main 中有執行緒組數量:0
B處執行緒:main 所屬的執行緒組名為:main 中有執行緒組數量:1
第一個執行緒組名稱為:新的組
*/
(5) 獲取根執行緒組
package chapter07.section02.thread_7_2_4.project_1_getGroupParent;
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());
}
}
/*
result:
執行緒:main 所在的執行緒組名為:main
main執行緒所在的執行緒組的父執行緒組的名稱是:system
Exception in thread "main" java.lang.NullPointerException
at chapter07.section02.thread_7_2_4.project_1_getGroupParent.Run.main(Run.java:15)
*/
(6) 執行緒組裡加執行緒組
package chapter07.section02.thread_7_2_5.project_1_mainGroup;
public class Run {
public static void main(String[] args) {
System.out.println("執行緒組名稱: "
+ Thread.currentThread().getThreadGroup().getName());
System.out.println("執行緒組中活動的執行緒數量: "
+ Thread.currentThread().getThreadGroup().activeCount());
ThreadGroup newGroup = new ThreadGroup(Thread.currentThread()
.getThreadGroup(), "newGroup");
System.out.println("執行緒組中執行緒組的數量-加之後: "
+ Thread.currentThread().getThreadGroup().activeGroupCount()); //main執行緒組中的一個執行緒組
System.out.println("父執行緒組名稱: "
+ Thread.currentThread().getThreadGroup().getParent().getName());
}
}
/*
result:
執行緒組名稱: main
執行緒組中活動的執行緒數量: 1
執行緒組中執行緒組的數量-加之後: 1
父執行緒組名稱: system
*/
(7) 組內的執行緒批量停止
package chapter07.section02.thread_7_2_6.project_1_groupInnerStop;
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 chapter07.section02.thread_7_2_6.project_1_groupInnerStop;
public class Run {
public static void main(String[] args) {
try {
ThreadGroup group = new ThreadGroup("Clarence");
for(int i = 0; i < 5; i++) {
MyThread thread = new MyThread(group, "執行緒" + (i + 1));
thread.start();
}
Thread.sleep(5000);
group.interrupt();
System.out.println("呼叫了interrupt()方法");
} catch (InterruptedException e) {
// TODO: handle exception
System.out.println("停了停了!");
e.printStackTrace();
}
}
}
/*
result:
ThreadName=執行緒1準備開始死迴圈了 :)
ThreadName=執行緒5準備開始死迴圈了 :)
ThreadName=執行緒4準備開始死迴圈了 :)
ThreadName=執行緒3準備開始死迴圈了 :)
ThreadName=執行緒2準備開始死迴圈了 :)
呼叫了interrupt()方法
ThreadName=執行緒5結束了 :)
ThreadName=執行緒1結束了 :)
ThreadName=執行緒3結束了 :)
ThreadName=執行緒4結束了 :)
ThreadName=執行緒2結束了 :)
*/
結果分析:
通過將執行緒歸屬到執行緒組中,當呼叫執行緒組ThreadGroup的interrupt()方法時,可以
將該組中的所有正在執行的執行緒批量停止
(8) 遞迴與非遞迴取得組內物件
package chapter07.section02.thread_7_2_7.project_1_groupRecurseTest;
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) {
// TODO: handle exception
e.printStackTrace();
}
}
};
ThreadGroup groupB = new ThreadGroup(groupA, "B");
//分配空間,但不一定全部用完
ThreadGroup listGroup1[] = new ThreadGroup[Thread.currentThread().
getThreadGroup().activeGroupCount()];
//傳入true是遞迴取得子組和子孫組 預設情況下是傳入true
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());
}
}
}
}
/*
result:
A
B
A
*/
3.使執行緒具有有序性
正常的情況下,執行緒在執行時多個執行緒之間執行任務的時機是無序的。可以通過改造程式碼
的方式使它們執行具有有序性。
package chapter07.section03.project_1_threadRunSyn;
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 chapter07.section03.project_1_threadRunSyn;
public class Run {
public static void main(String[] args) {
Object lock = new Object();
MyThread a = new MyThread(lock, "A", 1);
MyThread b = new MyThread(lock, "B", 2);
MyThread c = new MyThread(lock, "C", 0);
a.start();
b.start();
c.start();
}
}
/*
result:
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
*/
4. SimpleDateFormat非執行緒安全
類SimpleDateFormat主要負責日期的轉換與格式化,但在多執行緒的環境中,使用此類
容易造成資料轉換及處理的不準確,因為SimpleDateFormat類並不是執行緒安全的
檢視DateFormat的format(Date date)原始碼,我們可發現實現如下:
/**
* 格式化日期
*/
public final String format(Date date) {
return format(date, new StringBuffer(),
DontCareFieldPosition.INSTANCE).toString();
}
/**
* 真正的格式化由此類來實現
*/
public abstract StringBuffer format(Date date, StringBuffer toAppendTo,
FieldPosition fieldPosition);
繼續檢視SimpleDateFormat,可檢視到方法相關定義如下:
public StringBuffer format(Date date, StringBuffer toAppendTo,
FieldPosition pos) {
// 其實看到這裡已經明白了,如此輕易地使用內部變數,能執行緒安全
// 執行緒都對pos進行寫操作,必然會影響其他執行緒的讀操作
pos.beginIndex = pos.endIndex = 0;
return format(date, toAppendTo, pos.getFieldDelegate());
}
(1) 出現異常
package chapter07.section04;
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 chapter07.section04;
import java.text.SimpleDateFormat;
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();
}
}
}
/*出現異常*/
(2) 解決異常方法1
package chapter07.section05.project_1_threadCreateException;
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 chapter07.section05.project_1_threadCreateException;
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 = DateTools.parse("yyyy-MM-dd", dateString);
String newDateString = DateTools.format("yyyy-MM-dd", dateRef);
if (!newDateString.equals(dateString)) {
System.out.println("ThreadName=" + this.getName()
+ "報錯了 日期字串:" + dateString + " 轉換成的日期為:"
+ newDateString);
}
} catch (ParseException e) {
e.printStackTrace();
}
}
}
package chapter07.section05.project_1_threadCreateException;
import java.text.SimpleDateFormat;
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();
}
}
}
結果正確
(2) 解決異常方法2
ThreadLocal類能使執行緒繫結到指定的物件。使用該類也可以解決多執行緒環境下
SimpleDateFormat類處理錯誤的情況
package chapter07.section04.thread_7_4_3.project_1_formatOK2;
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 chapter07.section04.thread_7_4_3.project_1_formatOK2;
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 = 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 chapter07.section04.thread_7_4_3.project_1_formatOK2;
import java.text.SimpleDateFormat;
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();
}
}
}
5.執行緒中出現異常的處理
package chapter07.section05.project_1_threadCreateException;
public class MyThread extends Thread{
@Override
public void run() {
String username = null;
System.out.println(username.hashCode());
}
}
package chapter07.section05.project_1_threadCreateException;
public class Main1 {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}
}
/*
Exception in thread "Thread-0" java.lang.NullPointerException
at chapter07.section05.project_1_threadCreateException.MyThread.run(MyThread.java:7)
*/
package chapter07.section05.project_1_threadCreateException;
import java.lang.Thread.UncaughtExceptionHandler;
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();
}
}
/*
result:
執行緒:執行緒t1 出現了異常:
Exception in thread "執行緒t2" java.lang.NullPointerException
at chapter07.section05.project_1_threadCreateException.MyThread.run(MyThread.java:7)
java.lang.NullPointerException
at chapter07.section05.project_1_threadCreateException.MyThread.run(MyThread.java:7)
*/
報出空指標異常,在Java多執行緒技術中,可以對多執行緒中的異常進行"捕捉",使
用的是UncaughtExceptionHandler類,從而可以對發生的異常進行有效的處理
方法setUncaughtExceptionHandler()的作用是對指定的執行緒物件設定預設的異
常處理器。在Thread類中還可以使用setDefaultUncaughtExceptionHandler()
方法對所有執行緒物件設定異常處理器。
6. 執行緒組內處理異常
package chapter07.section06.project_1_threadGroup_1;
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 chapter07.section06.project_1_threadGroup_1;
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();
}
}
從執行結果來看,在預設的情況下,執行緒組中的一個執行緒出現異常不會影響其他
執行緒的執行
如果想實現執行緒組內一個執行緒出現異常後全部執行緒都停止執行該如何實現?
package chapter07.section06.project_2_threadGroup_2;
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 chapter07.section06.project_2_threadGroup_2;
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 chapter07.section06.project_2_threadGroup_2;
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();
}
}
使用自定義java.lang.ThreadGroup執行緒組,並且重寫uncaughtException方
法處理組內執行緒中斷行為時,每個執行緒物件中的run()方法內部不要有異常catc
語句,如果有catch語句,則public void uncaughtException(Thread t,
Throwable e)方法不執行
7. 執行緒異常處理的傳遞
具體看程式碼
結論:
方法setUncaughtExceptionHandler()是給指定執行緒物件設定異常處理器,
seDefaultUncaughtExceptionHandler()是給所有執行緒物件設定異常處理器,以及
執行緒組覆蓋ThreadGroup的uncaughtException(Thread t, Throwable e)方法。
異常的傳遞由前面特殊傳遞到後面普遍