一、線程基本機制
1、概念描述
并發程式設計的特點是:可以将程式劃分為多個分離且獨立運作的任務,通過線程來驅動這些獨立的任務執行,進而提升整體的效率。下面提供一個基礎的示範案例。
2、應用案例
場景:假設有一個容器集合,需要拿出容器中的每個元素,進行加工處理,一般情況下直接周遊就好,如果資料偏大,可以根據線程數量對集合切割,每個線程處理一部分資料,這樣處理時間就會減少很多。
public class ExtendThread01 {
public static void main(String[] args) {
List<Object> dataList = new ArrayList<>() ;
dataList.add("A");
dataList.add("B");
dataList.add("C");
// 把一個大的集合按照每個子集合的2個元素切割
List<List<Object>> splitList = splitList(dataList,2);
for (List<Object> list:splitList){
System.out.println(list);
}
// 多線程處理
for (List<Object> childList:splitList){
ListTask listTask = new ListTask(childList) ;
Thread runThread = new Thread(listTask);
runThread.start();
}
}
/**
* List 集合切割
*/
private static List<List<Object>> splitList (List<Object> list, int childSize) {
if (list == null || list.size() == 0 || childSize < 1) {
return null;
}
List<List<Object>> result = new ArrayList<>();
int size = list.size();
int count = (size + childSize - 1) / childSize ;
for (int i = 0; i < count; i++) {
List<Object> subList = list.subList(i * childSize, ((i + 1) * childSize > size ? size : childSize * (i + 1)));
result.add(subList);
}
return result;
}
}
class ListTask implements Runnable {
private List<Object> list ;
public ListTask (List<Object> list){this.list=list;}
@Override
public void run() {
for (Object object:list){
System.out.println(Thread.currentThread().getName()+"=="+object);
}
}
}
注意:這裡案例隻是對場景原理的實作,在開發中,是不允許這種操作的,需要使用線程池處理,後續會說。如果集合沒有控制好,導緻大量建立Thread線程,導緻記憶體溢出。
二、線程停止啟動
1、基礎流程
線程啟動後執行任務方法,在執行過程中可以被阻塞,休眠,喚醒,停止等一系列狀态操作。
線程休眠作用:當線程一部分任務執行完畢後進入休眠(阻塞)狀态,線程排程器可以切換到另外線程,這對分布任務的執行相對公平。
2、使用案例
public class ExtendThread02 {
public static void main(String[] args) {
StopThread stopThread = new StopThread() ;
stopThread.start();
// 标記目前線程停止信号,且抛出中斷異常,但沒有停止
stopThread.interrupt();
// 判斷目前線程是否已經是終止狀态
System.out.println("1=="+stopThread.isInterrupted());
// 清除目前線程的終止信号
System.out.println("2=="+stopThread.interrupted());
// 再次判斷目前線程狀态
System.out.println("3=="+stopThread.isInterrupted());
System.out.println("main end ...");
}
}
class StopThread extends Thread {
@Override
public void run() {
for (int i = 0 ; i < 10 ; i++){
try {
System.out.println(Thread.currentThread().getId()+"="+i);
// 線程阻塞1秒
Thread.sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
3、核心方法
sleep(long millis)
:線程休眠指定的時間,進入阻塞狀态;
interrupt()
:切換線程為中斷狀态,抛出中斷異常,不會停止線程,可以監視線程的中斷狀态并定義執行政策。
interrupted()
:清除調用該方法線程的中斷狀态,也不會影響線程的執行,且傳回目前執行‘stopThread.interrupted()’的線程是否中斷,這裡就是指main線程是否中斷。
isInterrupted()
:判斷調用該方法的線程是否已經是中斷狀态。
補刀一句
:線程的這幾個方法極其容易混淆,需要斷點源碼追蹤一下看看,進入源碼方法,調用相關API檢視一下狀态。(附斷點圖一張:)

三、線程優先級
1、基礎概念
CPU執行和處理線程的順序是不确定的,但是線程排程器傾向執行線程優先級高的線程,線程優先級高說明擷取CPU資源的機率高,或者擷取的執行時間分片多,但不代表優先級低的一定最後執行。
public class ExtendThread03 {
public static void main(String[] args) {
Priority01 priority01 = new Priority01();
priority01.start();
System.out.println("priority01="+priority01.getPriority());
Priority02 priority02 = new Priority02();
priority02.start();
System.out.println("priority02="+priority02.getPriority());
priority01.setPriority(10);
priority02.setPriority(1);
}
}
class Priority01 extends Thread {
@Override
public void run() {
for (int i = 0 ; i < 100 ; i++){
System.out.println(Thread.currentThread().getName()+";i="+i);
}
}
}
class Priority02 extends Thread {
@Override
public void run() {
for (int a = 0 ; a < 100 ; a++){
System.out.println(Thread.currentThread().getName()+";a="+a);
}
}
}
注意
:優先級範圍[MAX_PRIORITY=10,MIN_PRIORITY=1],如果超出範圍會抛出IllegalArgumentException異常。
建議
:通常實際開發中,是不允許輕易修改線程運作的參數,容易引發認知之外的異常。
四、線程加入
1、基本概念
如果線上程A中,執行線程B的加入方法,那麼A線程就會等待線程B執行完畢再傳回繼續執行。
public class ExtendThread04 {
public static void main(String[] args) {
JoinThreadA joinThreadA = new JoinThreadA() ;
joinThreadA.start();
}
}
class JoinThreadA extends Thread {
@Override
public void run() {
System.out.println("缺水中...");
JoinThreadB joinThreadB = new JoinThreadB() ;
joinThreadB.start();
try{
joinThreadB.join();
} catch (Exception e){
e.printStackTrace();
}
System.out.println("喝水中...");
}
}
class JoinThreadB extends Thread {
@Override
public void run() {
System.out.println("買水中...");
try{
TimeUnit.SECONDS.sleep(2);
} catch (Exception e){
e.printStackTrace();
}
System.out.println("買到水...");
}
}
注意
:可以設定線程的加入時間join(long),畢竟等不到雪月風花,人生都是有時差,隻能後會無期了。
五、本地線程
本地的線程變量,底層維護ThreadLocalMap存儲值:
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
即以Key-Value鍵值對的方式存儲資料。如果對集合容器的源碼熟悉的話,這個Entry就是似曾相識感覺。
public class ExtendThread05 {
private static final ThreadLocal<Long> threadLocal = new ThreadLocal<>() ;
private static void initBegin (){
threadLocal.set(System.currentTimeMillis());
}
private static Long overTime (){
return System.currentTimeMillis()-threadLocal.get();
}
public static void main(String[] args) throws Exception {
ExtendThread05.initBegin();
TimeUnit.SECONDS.sleep(3);
System.out.println(ExtendThread05.overTime());
}
}
ThreadLocal提供線程記憶體儲變量的能力,并且綁定到目前線程,通過get和set方法就可以得到和設定目前線程對應的值。這個在web開發中是常見的應用。
六、守護線程
守護線程是支援輔助型線程,主要在程式中起到排程和支援性作用,當Jvm中非守護線程全部結束,守護線程也就會結束。
public class ExtendThread06 {
public static void main(String[] args) throws Exception {
InputStreamReader is = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(is);
String value = br.readLine();
CheckThread checkThread = new CheckThread(value) ;
checkThread.setDaemon(true);
checkThread.start();
System.out.println("Main End ...");
}
}
class CheckThread extends Thread {
private String spell ;
public CheckThread (String spell){
this.spell = spell ;
}
@Override
public void run() {
if (spell.startsWith("cs")){
System.out.println(spell+":輸入正确");
} else {
System.out.println(spell+":輸入錯誤");
}
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
注意
:守護線程需要顯式調用setDaemon(true);方法,這裡可以看到main線程結束,整合程式就結束了,絲毫理會休眠中的守護線程。如果打斷非守護線程的休眠,試試會不會抛你一臉異常?
七、線程異常處理
1、機制描述
按照Java中異常處理機制,抛異常逐級降低,線程的任務方法run沒有抛異常,那重寫或者實作的方法自然不能直接throws異常出去。多線程中推薦捕獲異常,可以針對性處理機制。
public class ExtendThread07 {
public static void main(String[] args) {
TryThread tryThread = new TryThread();
tryThread.setName("try-name");
// 定義運作中異常處理政策
MyExe myExe = new MyExe() ;
tryThread.setUncaughtExceptionHandler(myExe);
tryThread.start();
}
}
class TryThread extends Thread {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
// 如何處理這裡異常?
Integer.parseInt("cicada") ;
}
}
class MyExe implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println(t.getName()+";異常:"+e.getMessage());
}
}
通過實作UncaughtExceptionHandler接口,并且線程要指定自定義異常處理對象,也可以處理未檢查的異常。
八、源代碼位址
GitHub·位址
https://github.com/cicadasmile/java-base-parent
GitEE·位址
https://gitee.com/cicadasmile/java-base-parent