這篇 Java 多執行緒,你一定能看懂學會!
前幾天學習了 Java 多執行緒,作為一個懶癌後期的患者,一直拖到現在才把所學的記錄下來,也算是複習了一遍 ?。希望大家多多支援喔!
在學習執行緒之前,我們先來了解一下程式吧!
程式
概述:正在執行的程式就是程式。程式是系統進行資源分配和呼叫的獨立單位,每一個程式都有它自己的記憶體空間和系統資源。
通過工作管理員,我們可以看到我們電腦現在的程式有哪些:
多程式的意義:計算機可以在一個時間段內同時執行多個任務,可以提高CPU的使用率。
思考:我們一邊聽音樂(網易雲程式),一邊寫程式碼(IDEA程式),這兩個任務是同時的嗎?
對於多核CPU 它有可能是同時的,但是對於單核CPU來說,它在某一個時間點,它只能做一件事情。
但是我們在聽音樂的時候,同時在寫程式碼,我們感官上,這兩個任務是同時進行的。但是實際CPU在執行程式的時候進行了程式間的高速切換,這個切換時間非常的短,所以我們就感覺兩個程式是在同時進行的。
執行緒
在同一個程式中,可以同時執行多個任務。而這每一個任務,就是一個執行緒。
執行緒:是程式的執行單元,也是執行路徑。執行緒是程式使用CPU資源的最基本單位。
單執行緒:只有一個執行單元或只有一條執行路徑
多執行緒:有多個執行單元或多個執行路徑
例如:我們平時寫的這些程式使單執行緒的
public class Test {
public static void main(String[] args) {
System.out.println("程式碼塊1");
method();
System.out.println("程式碼塊2");
}
public static void method() {
System.out.println("程式碼塊3");
function1();
function2();
System.out.println("程式碼塊4");
}
private static void function1() {
}
private static void function2() {
}
}
多執行緒的意義:
- 執行緒的執行是搶佔式的。每一個執行緒都要去搶佔CPU資源(CPU執行權)。一個多執行緒的程式在執行時,如果一些執行緒必須等待的時候,CPU就會將資源提供給其他執行緒去使用這些資源。這樣的話就提高了CPU的使用率。
- 對於程式來說,如果它是多執行緒的,在搶佔CPU資源時,就有更大的機率搶佔到CPU資源。提高該程式使用率。
多執行緒
實現方式一:
繼承 Thread 類,重寫 run() 方法。
步驟:
1 自定義 MyThread 類,繼承 Thread 類
2 重寫 run() 方法
3 建立 MyThread 物件
4 啟動執行緒
public class MyThread extends Thread {
// 重寫 run() 方法
@Override
public void run() {
for (int i = 0; i < 300; i++) {
System.out.println(i);
}
}
}
public class MyThreadDemo {
public static void main(String[] args) {
// 建立 MyThread 物件
MyThread myThread = new MyThread();
// 啟動執行緒
myThread.start();
}
}
注:run() 和 start() 的區別是什麼?
run():僅僅封裝了執行緒所執行的程式碼,直接呼叫和普通方法沒有區別。
start():首先啟動執行緒,然後由 JVM 呼叫該執行緒的 run() 方法。
- 獲取執行緒名稱:
public final String getName()
:返回此執行緒的名稱。
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 300; i++) {
System.out.println(getName() + i);
}
}
}
- 設定執行緒名稱:
法一:
public final void setName(String name)
:將此執行緒的名稱更改為引數 name
myThread.setName("執行緒一");
法二:構造方法
public class MyThread extends Thread {
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 300; i++) {
System.out.println(getName() + i);
}
}
}
public class MyThreadDemo {
public static void main(String[] args) {
// 使用有參構造:
MyThread myThread = new MyThread("執行緒一:");
myThread.start();
MyThread myThread1 = new MyThread("執行緒二:");
myThread1.start();
}
}
- 獲取當前正在執行的執行緒名稱:
public static Thread currentThread()
:返回對當前正在執行的執行緒物件的引用
String name = Thread.currentThread().getName();
System.out.println(name);
- 執行緒優先順序:
執行緒有兩種排程模型:
1、分時排程模型:所有的執行緒輪流使用 CPU,平均分配每個執行緒佔用 CPU 的時間段。
2、搶佔式排程模型:Java 是搶佔式排程模型,會優先讓優先順序高的執行;優先順序相同的執行緒,隨機執行一個。(注:優先順序高只代表它搶到 CPU 的概率較大,不一定必須是先執行的)
獲取優先順序的方法:
public final int getPriority()
:返回此執行緒的優先順序
設定優先順序的方法:
public final int setPriority(int newPriority)
:設定此執行緒的優先順序
// 設定執行緒優先順序。
myThread1.setPriority(10);
myThread3.setPriority(1);
// 獲取執行緒優先順序。
System.out.println(myThread1.getPriority());
System.out.println(myThread2.getPriority());
System.out.println(myThread3.getPriority());
/*
輸出的結果:
10
5
1
*/
注:1. 預設優先順序是5
2. 優先順序的取值範圍是 1-10
執行緒控制
- sleep():執行緒睡眠
public static void sleep(long miles)
:導致當前正在執行的執行緒休眠(暫時停止執行)指定的毫秒數
public class MyThread3 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("有異常!");
}
System.out.println(getName() + "-" + i);
}
}
}
public class MyThreadDemo3 {
public static void main(String[] args) {
MyThread3 myThread1 = new MyThread3();
MyThread3 myThread2 = new MyThread3();
MyThread3 myThread3 = new MyThread3();
myThread1.setName("喜羊羊");
myThread2.setName("美羊羊");
myThread3.setName("灰太狼");
myThread1.start();
myThread2.start();
myThread3.start();
}
}
執行上面的程式,可以看到每個執行緒都是輸出一次之後等待一秒再繼續輸入
- interrupt():執行緒中斷
public void interrupt()
:中斷執行緒
public class MyThread7 extends Thread {
@Override
public void run() {
System.out.println("執行緒開始執行" + new Date());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out.println("程式出現錯誤!");
}
System.out.println("執行緒結束執行" + new Date());
}
}
public class MyThreadDemo7 {
public static void main(String[] args) {
MyThread7 myThread1 = new MyThread7();
//啟動執行緒
myThread1.start();
// myThread1 休眠時間超過三秒,就終止它。
try {
// 主執行緒休眠三秒。
Thread.sleep(3000);
myThread1.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- join():執行緒加入
public void join()
:等待該執行緒終止
public class MyThread4 extends Thread {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(getName() + ": " + i);
}
}
}
public class MyThreadDemo4 {
public static void main(String[] args) {
// 建立執行緒類物件
MyThread4 myThread1 = new MyThread4();
MyThread4 myThread2 = new MyThread4();
MyThread4 myThread3 = new MyThread4();
// 設定名稱。
myThread1.setName("執行緒一");
myThread2.setName("執行緒二");
myThread3.setName("執行緒三");
myThread1.start();
// 加入執行緒。
try {
myThread1.join();
} catch (InterruptedException e) {
System.out.println("出錯了!");
}
myThread2.start();
myThread3.start();
}
}
執行上面程式,執行緒一執行完畢後,後面兩個執行緒才開始搶佔資源,進行執行
- yield():執行緒禮讓
public static void yield()
:暫停當前正在執行的執行緒物件,並執行其他執行緒(可以減小搶佔競爭)
public class MyThread5 extends Thread {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(getName() + ": " + i);
// 執行緒禮讓
Thread.yield();
}
}
}
public class MyThreadDemo05 {
public static void main(String[] args) {
// 建立執行緒類物件
MyThread5 myThread1 = new MyThread5();
MyThread5 myThread2 = new MyThread5();
// 設定名稱。
myThread1.setName("Andy");
myThread2.setName("Jay");
myThread1.start();
myThread2.start();
}
}
- setDeman():執行緒守護
public final void setDaemon(boolean on)
:將該執行緒標記為守護執行緒或使用者執行緒(當正在執行的執行緒都是守護執行緒時,Java 虛擬機器退出,程式結束)
public class MyThread6 extends Thread {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(getName() + ": " + i);
}
}
}
public class MyThreadDemo6 {
public static void main(String[] args) {
MyThread6 myThread1 = new MyThread6();
MyThread6 myThread2 = new MyThread6();
myThread1.setName("執行緒一");
myThread2.setName("執行緒二");
// 守護執行緒設定為true。主基地結束,其餘兩個執行緒一會也會結束。
myThread1.setDaemon(true);
myThread2.setDaemon(true);
myThread1.start();
myThread2.start();
// 獲取主執行緒。
Thread.currentThread().setName("主基地:");
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
執行緒的生命週期:
實現方式二:
實現 Runnable 介面,重寫 run() 方法。然後可以分配類的例項,在建立 Thread 時作為引數傳遞。
步驟:
1.建立自定義執行緒類。
2.重寫 run 方法
3.建立自定義執行緒類物件
4.建立多個Thread類物件,將自定義執行緒類物件作為引數傳遞。
5.啟動執行緒。
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
// getName() 是Thread的方法,所以在這裡應該這樣獲取執行緒名稱
System.out.println(Thread.currentThread().getName() + i);
}
}
}
public class MyRunnableDemo {
public static void main(String[] args) {
// 建立自定義執行緒類物件
MyRunnable mr = new MyRunnable();
// 建立多個Thread類物件,將自定義執行緒類物件作為引數傳遞
Thread t1 = new Thread(mr, "執行緒一");
Thread t2 = new Thread(mr, "執行緒二");
// 啟動執行緒
t1.start();
t2.start();
}
}
注:為什麼建立執行緒的第一種方法還要有第二種方法?
- 繼承只能單繼承,如果自定義執行緒類有父類,則它不能再繼承Thread
- 第二種方式適合多個執行緒操作同一個資源這種情況,比較簡潔。把執行緒和程式程式碼 資料進行有效分離,較好地體現了物件導向的思想。
案例:共有150張票,建立三個執行緒,模擬電影院三個視窗的賣票情況。
public class MyRunnable implements Runnable {
// 如果這裡是繼承 Thread 類,則需要用static來修飾票數,以保證三個執行緒共享同一個資源,三個視窗共賣這150張票。
private int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
try {
// 根據現實情況,賣票會出現延遲
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在售賣第" + (ticket--) + "張票。");
}
}
}
}
執行上面程式,會發現有一票多賣和負數票的情況,該問題與執行緒安全有關。
執行緒安全:
出現原因:
- 多執行緒環境
- 存在共享資料
- 存在多條語句操作該共享資料
為了解決這個問題可以將操作共享資料的這段程式碼包裹起來,在有執行緒訪問這段程式碼時,其他執行緒不能訪問。
同步程式碼塊:
synchronize
關鍵字:
synchronize(物件名){
多條語句;
}
同步程式碼塊的物件是任意物件。
如果給這些執行緒傳遞同一個物件,就是相當於給了一個門,一把鎖;如果傳遞不同的物件,就相當於有多個門,多把鎖。
public class MyRunnable implements Runnable {
private int tickets = 50;
private Object object = new Object();
@Override
public void run() {
while (true) {
synchronized (object) {
if (tickets > 0) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "賣出去標號為 " + (tickets--) + "的這張票");
}
}
}
}
}
public class MyRunnableDemo {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr, "視窗一:");
Thread t2 = new Thread(mr, "視窗二:");
Thread t3 = new Thread(mr, "視窗三:");
t1.start();
t2.start();
t3.start();
}
}
注:如果使用繼承 Thread 類的方法,則需要將票數和鎖物件宣告為靜態的,以保證為所有物件共用。
private static int ticket = 50;
private static Object obj = new Object();
執行以上程式碼,上面的一票多賣和負數票的問題都被解決了。
同步程式碼塊的優缺點:
優點:解決了執行緒安全問題。
缺點:每個執行緒在執行前都要去判斷鎖物件,無形中增加了電腦負擔。
同步方法:
格式一:synchronized
許可權修飾符 返回值型別 方法名()
格式二:許可權修飾符 synchronized
返回值型別 方法名()
public class SaleTicket implements Runnable {
private int ticket = 50;
@Override
public void run() {
ticket();
}
private synchronized void ticket() {
while (true) {
if (ticket > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在售賣第" + (ticket--) + "張票");
}
}
}
}
public class SaleTicketDemo {
public static void main(String[] args) {
SaleTicket st = new SaleTicket();
Thread t1 = new Thread(st, "視窗一:");
Thread t2 = new Thread(st, "視窗一:");
Thread t3 = new Thread(st, "視窗一:");
t1.start();
t2.start();
t3.start();
}
}
同步方法的鎖物件是this,而如果該同步方法是靜態的,由於靜態方法隨著類的載入而載入,那麼它的鎖物件是該類的位元組碼檔案(類名.class)。
Lock鎖
Lock
:這是一個介面,實現了比 synchronize
更語句和方法更廣泛的操作。
實現子類:ReentrantLock
。
成員方法:void lock()
上鎖;void unlock
解鎖。
public class SaleTicket implements Runnable {
private int tickets = 50;
// 使用多型的方法建立鎖物件
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
// 上鎖。
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "賣出去標號為 " + (tickets--) + "的這張票");
}
// 解鎖
lock.unlock();
}
}
}
public class SaleTicketDemo {
public static void main(String[] args) {
SaleTicket s = new SaleTicket();
Thread thread1 = new Thread(s, "視窗一:");
Thread thread2 = new Thread(s, "視窗二:");
Thread thread3 = new Thread(s, "視窗三:");
thread1.start();
thread2.start();
thread3.start();
}
}
注:上鎖和解鎖的位置和同步程式碼塊的位置相同。
死鎖:
同步程式碼塊的弊端:效率低,而且如果出現了同步巢狀,就容易產生死鎖的問題。
死鎖:是指兩個或兩個以上的執行緒在執行的過程中,因爭奪資源產生的一種互相等待的現象。
public class deadLock extends Thread {
private boolean flag;
private static Object lockA = new Object();
private static Object lockB = new Object();
// 構造方法
public deadLock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
synchronized (lockA) {
System.out.println("if lockA");
synchronized (lockB) {
System.out.println("if lockB");
}
}
} else {
synchronized (lockB) {
System.out.println("else lockB");
synchronized (lockA) {
System.out.println("else lockA");
}
}
}
}
}
public class deadLockDemo {
public static void main(String[] args) {
deadLock deadLock1 = new deadLock(true);
deadLock deadLock2 = new deadLock(false);
deadLock1.start();
deadLock2.start();
}
}
生產者消費者模型
執行緒間通訊:不同種類的執行緒針對同一個資源進行操作
例如:不同執行緒操作同一個學生物件,一個執行緒用來設定學生物件,一個執行緒用來獲取學生物件。
public class Student {
private String name;
private int age;
// 無參構造
// 有參構造
// get,set 方法
// 此處省略
}
public class SetThread implements Runnable {
private Student student;
private int x = 0;
public SetThread(Student student) {
this.student = student;
}
@Override
public void run() {
while (true) {
synchronized (student) {
if (x % 2 == 0) {
student.setName("William");
student.setAge(30);
} else {
student.setName("Andy");
student.setAge(5);
}
x++;
}
}
}
}
public class GetThread implements Runnable {
private Student student;
public GetThread(Student student) {
this.student = student;
}
@Override
public void run() {
while (true) {
synchronized (student) {
System.out.println(student.getName() + "---" + student.getAge());
}
}
}
}
public class StudentDemo {
public static void main(String[] args) {
// 建立學生物件
Student student = new Student();
// 建立自定義執行緒類物件
SetThread setThread = new SetThread(student);
GetThread getThread = new GetThread(student);
// 建立Thread類物件,將自定義執行緒類物件作為引數傳遞
Thread thread = new Thread(setThread,"執行緒一");
Thread thread2 = new Thread(getThread,"執行緒二");
// 啟動執行緒
thread.start();
thread2.start();
}
}
生產者消費者模型執行緒安全問題
出現問題:
a. 相同資料出現多次()
b. 姓名和年齡不匹配(CPU執行的原子性和執行緒的隨機性導致)
等待喚醒機制
問題:
第一次執行的時候,如果消費者先搶到 CPU 執行權,它就會去消費資料,但是此時的資料是預設值,沒有任何意義,所以應該等待生產者生產完資料之後,再去消費。
如果生產者搶到 CPU 執行權,它就會生產資料,但是,如果下一次都是生產者搶到 CPU 執行權,它就會重複生產資料,這樣是不合理的。應該等待消費者消費掉資料之後,再繼續生產。
解決思路:
生產者:先看是否有資料,如果有,就等待,如果沒有就生產資料。生產完畢之後通知消費者前來消費。
消費者:先看是否有資料,如果沒有,就等待,如果有,就消費。消費完之後通知生產者生產資料。
wait()
:執行緒等待
notify()
:喚醒等待的執行緒
執行緒組
當專案中有許多執行緒需要設定一些相同的屬性,比如都設定成守護執行緒。我們可以考慮根據執行緒的功能或者用途進行分組,然後針對組進行統一管理,這樣的好處是方便分類操作和管理。
預設分組:
public static void method1() {
MyThreadGroup tg = new MyThreadGroup();
Thread thread1 = new Thread(tg);
Thread thread2 = new Thread(tg);
System.out.println(thread1.getThreadGroup().getName());
System.out.println(thread2.getThreadGroup().getName());
System.out.println(Thread.currentThread().getThreadGroup().getName());
}
設定分組:
public static void method2() {
ThreadGroup threadGroup = new ThreadGroup("守護執行緒");
MyThreadGroup tg = new MyThreadGroup();
Thread thread1 = new Thread(threadGroup, tg);
Thread thread2 = new Thread(threadGroup, tg);
System.out.println(thread1.getThreadGroup().getName());
System.out.println(thread2.getThreadGroup().getName());
System.out.println(Thread.currentThread().getThreadGroup().getName());
// 將這個組設定成守護執行緒
threadGroup.setDaemon(true);
}
執行緒池
在建立執行緒時,成本是比較高的,因為每一次建立執行緒時,都要與作業系統互動。而執行緒池會在程式啟動時,提前建立一些執行緒放線上程池中等待使用,這樣可以大大的提高執行效率。
特點:執行緒執行完畢後,不會死亡,而是重新回到執行緒池中,成為空閒狀態等下下一個執行緒使用。
JDK5 之前需要手動配置執行緒池,JDK5 之後Java開始內建執行緒池。
Executors
:工廠類
通過下面的方法獲得執行緒池物件:
1、
2、public static ExecutorService newFixedThreadPool(int nThreads)
:建立一個可重用固定執行緒數的執行緒池
3、public static ExecutorService newSingleThreadExecutor()
:建立一個使用單個 worker 執行緒的 Executor
這些方法的返回值是 ExecutorService
物件,該物件表示一個執行緒池,它可以執行 Runnable物件 或者 Callable物件 物件代表的執行緒池。
操作步驟:
1、建立執行緒池物件
2、建立自定義類物件
3、提交 MyRunnable 到執行緒池
4、關閉執行緒池
public class ThreadPoolDemo {
public static void main(String[] args) {
// 1 建立執行緒池物件
ExecutorService es = Executors.newFixedThreadPool(2);
// 2 建立自定義執行緒類的物件
MyThreadPool mtp = new MyThreadPool();
// 3 提交Runnable例項
es.submit(mtp);
es.submit(mtp);
// 4 關閉執行緒池
es.shutdown();
}
}
多執行緒第三種實現方式: Callable
Callable:這是一個介面,類似於 Runnable 介面,但是,Callable有返回值,並且可以丟擲異常。
所以,如果某些執行緒執行完畢後需要給我們返回一個執行結果時,我們可以使用Callable介面這些方式來實現多執行緒。
public class MyCallable implements Callable {
@Override
public Object call() throws Exception {
for (int i = 0; i < 30; i++) {
System.out.println(i);
}
return null;
}
}
public class MyCallableDemo {
public static void main(String[] args) {
// 1 建立執行緒池物件
ExecutorService es = Executors.newFixedThreadPool(2);
// 2 建立自定義類物件
MyCallable mc = new MyCallable();
// 3 提交 Callable 物件
es.submit(mc);
es.submit(mc);
}
}
案例:使用兩個執行緒分別求 1-100 的和(泛型的使用)
public class MyCallable implements Callable<Integer> {
private int num;
public MyCallable(int num) {
this.num = num;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i < num; i++) {
sum += i;
}
return sum;
}
}
public class MyCallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService es = Executors.newFixedThreadPool(2);
MyCallable mc = new MyCallable(100);
MyCallable mc2 = new MyCallable(200);
Future<Integer> future = es.submit(mc);
Future<Integer> future2 = es.submit(mc2);
int num = future.get();
int num2 = future2.get();
System.out.println(num);
System.out.println(num2);
es.shutdown();
}
}
使用匿名內部類實現多執行緒
匿名內部類格式:
new 類名或介面名(){
重寫方法;
};
new Thread(){
@Override
public void run() {
for (int i = 0; i < 30; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
};
new Thread(
new Runnable() {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
){};
new Thread(
new Runnable() {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
System.out.println("hello" + i);
}
}
}
){
@Override
public void run() {
for (int i = 0; i < 30; i++) {
System.out.println("world" + i);
}
}
};
注:當同時重寫了 Thread類 和 Runnable介面 中的 run() 方法,執行時執行的是 Thread類 中的 run() 方法。
定時器
定時器是一個應用十分廣泛的執行緒工具,可用於排程多個定時任務以後臺的方式執行,可以通過 Timer 和 TimerTask 類來實現定義和排程的功能。
概念:可以指定程式在某個指定的時間做某件工作。
Timer :執行緒的工具,用於在後臺執行緒中安排將來執行的任務。可以將任務安排為一次性執行,或者以固定間隔重複執行。
方法:
Timer()
:建立一個新的定時器。
public void schedule(TimerTask task, long delay)
:在指定毫秒時間後執行task任務。
public void schedule(TimerTask task, long delay, long period)
:在指定毫秒時間後執行task任務,並在指定間隔時間後再次執行。
TimerTask :可由 Timer 一次性或重複執行的任務。
方法:
public boolean cancel()
:取消此定時器任務。
public abstract void run()
:重寫run()方法,重寫的內容即要執行的任務。
public class TimerDemo {
public static void main(String[] args) {
// 建立定時器物件
Timer timer = new Timer();
// 執行 Task 任務
timer.schedule(new MyTask(timer), 3000);
}
}
class MyTask extends TimerTask {
private Timer timer;
public MyTask(Timer timer) {
this.timer = timer;
}
@Override
public void run() {
System.out.println("有內鬼,終止交易");
timer.cancel();
}
}
執行以上程式碼,可以看到三秒之後執行 run() 方法中的語句。
我是快鬥,請多多指教!
相關文章
- Java多執行緒(學習篇)Java執行緒
- 看完這篇多執行緒,再說多執行緒學不會,那你就收藏多看兩遍執行緒
- Java多執行緒(一)多執行緒入門篇Java執行緒
- 你會這道阿里多執行緒面試題嗎?阿里執行緒面試題
- Java多執行緒學習(一)Java多執行緒入門Java執行緒
- 細說 Android 下的多執行緒,學會了多執行緒,你就學會了壓榨CPU!Android執行緒
- Java多執行緒-基礎篇Java執行緒
- Java多執行緒詳解——一篇文章搞懂Java多執行緒Java執行緒
- Java多執行緒學習Java執行緒
- Java多執行緒學習——執行緒通訊Java執行緒
- Java多執行緒學習(2)執行緒控制Java執行緒
- Java多執行緒——執行緒Java執行緒
- Java多執行緒之進階篇Java執行緒
- JAVA多執行緒-基礎篇-synchronizedJava執行緒synchronized
- 嗯!這篇多執行緒不錯!伍執行緒
- #大學#Java多執行緒學習02(執行緒同步)Java執行緒
- 執行緒、多執行緒和執行緒池,看完這些你就能全部搞懂了執行緒
- 面試官:這就是你理解的Java多執行緒基礎?面試Java執行緒
- Java 多執行緒NIO學習Java執行緒
- Java多執行緒-執行緒中止Java執行緒
- Java多執行緒——執行緒池Java執行緒
- Java多執行緒學習(3)執行緒同步與執行緒通訊Java執行緒
- 【Java多執行緒】輕鬆搞定Java多執行緒(二)Java執行緒
- Java多執行緒就是這麼簡單Java執行緒
- Java多執行緒學習筆記(六) 長樂未央篇Java執行緒筆記
- 學習java多執行緒,這必須搞懂的這幾個概念Java執行緒
- java——多執行緒Java執行緒
- java 多執行緒Java執行緒
- 【Java】多執行緒Java執行緒
- JAVA 多執行緒 ??Java執行緒
- java多執行緒Java執行緒
- Java - 多執行緒Java執行緒
- java 多執行緒守護執行緒Java執行緒
- Java多執行緒-執行緒通訊Java執行緒
- Java多執行緒-執行緒狀態Java執行緒
- Java多執行緒(2)執行緒鎖Java執行緒
- java多執行緒9:執行緒池Java執行緒
- Java多執行緒之執行緒中止Java執行緒