1、多線程的目的
即“最大限度的利用CPU資源”,當(dāng)某一線程的處理不需要占用CPU而只和I/O等資源打交道時,讓需要占用CPU資源的其他線程有機(jī)會獲得CPU資源。
2、創(chuàng)建線程
方法一:
通過繼承Thread類創(chuàng)建線程
- 普通線程如果繼承自Thread類,就成為了一個線程類,并可以通過該類的start方法來啟動線程,執(zhí)行線程代碼。
*Thread類的子類可以直接實例化,但在子類中必須覆蓋run方法才能真正運(yùn)行線程代碼。
class MyThread extends Thread {
private int i = 0;
@Override
public void run() {
for (i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 30) {
Thread myThread1 = new MyThread(); // 創(chuàng)建一個新的線程 myThread1 此線程進(jìn)入新建狀態(tài)
Thread myThread2 = new MyThread(); // 創(chuàng)建一個新的線程 myThread2 此線程進(jìn)入新建狀態(tài)
myThread1.start(); // 調(diào)用start()方法使得線程進(jìn)入就緒狀態(tài)
myThread2.start(); // 調(diào)用start()方法使得線程進(jìn)入就緒狀態(tài)
}
}
}
}
方法二:
通過實現(xiàn)Runnable接口創(chuàng)建線程
- 實現(xiàn)Runnable接口的類必須借助Thread類才能創(chuàng)建線程。通過Runnable接口創(chuàng)建線程分為兩步:
1、創(chuàng)建實現(xiàn)Runnable接口的類的實例。
2、創(chuàng)建一個Thread類對象,將第一步得到的實例作為參數(shù)傳入Thread類的構(gòu)造方法。 - 通過Tread類的start方法啟動線程。
class MyRunnable implements Runnable {
private int i = 0;
@Override
public void run() {
for (i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 30) {
Runnable myRunnable = new MyRunnable(); // 創(chuàng)建一個Runnable實現(xiàn)類的對象
Thread thread1 = new Thread(myRunnable); // 將myRunnable作為Thread target創(chuàng)建新的線程
Thread thread2 = new Thread(myRunnable);
thread1.start(); // 調(diào)用start()方法使得線程進(jìn)入就緒狀態(tài)
thread2.start();
}
}
}
}
當(dāng)然,還可以直接使用匿名內(nèi)部類,方法十分簡便,僅需重寫run方法,例如:
/*
* 匿名內(nèi)部類的格式:
*/
public class ThreadDemo {
public static void main(String[] args) {
// 繼承thread類實現(xiàn)多線程
new Thread() {
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + "--"
+ x);
}
}
}.start();
;
// 實現(xiàn)runnable借口,創(chuàng)建多線程并啟動
new Thread(new Runnable() {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + "--"
+ x);
}
}
}) {
}.start();
// 更有難度的,在Thread匿名內(nèi)部類的里面再一次重寫run方法
//在實際運(yùn)行時的結(jié)果是 hello+x。以thread的run方法為準(zhǔn)。但是此處無意義
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
for (int x = 0; x < 100; x++) {
System.out.println("java" + "--" + x);
}
}
}) {
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println("hello" + "--" + x);
}
}
}.start();
}
}
3、線程完整生命周期

生命周期



4、線程同步
線程同步是為了防止多個線程訪問一個數(shù)據(jù)對象時,對數(shù)據(jù)造成破壞。


//示例
public synchronized void addAmount(double amount) {
}
synchronized(this){
}
5、線程通信
假設(shè)有這么一個問題,要求編寫兩個線程,一個線程打印125,另一個線程打印字母AZ,打印順序為12A34B56C……5152Z,這就要求使用線程間的通信,解決的方法有以下幾種:
- 使用一個共享變量控制,利用最基本的synchronized、notify、wait
public class MethodOne {
private final ThreadToGo threadToGo = new ThreadToGo();
public Runnable newThreadOne() {
final String[] inputArr = Helper.buildNoArr(52);
return new Runnable() {
private String[] arr = inputArr;
public void run() {
try {
for (int i = 0; i < arr.length; i=i+2) {
synchronized (threadToGo) {
while (threadToGo.value == 2)
threadToGo.wait();
Helper.print(arr[i], arr[i + 1]);
threadToGo.value = 2;
threadToGo.notify();
}
}
} catch (InterruptedException e) {
System.out.println("Oops...");
}
}
};
}
public Runnable newThreadTwo() {
final String[] inputArr = Helper.buildCharArr(26);
return new Runnable() {
private String[] arr = inputArr;
public void run() {
try {
for (int i = 0; i < arr.length; i++) {
synchronized (threadToGo) {
while (threadToGo.value == 1)
threadToGo.wait();
Helper.print(arr[i]);
threadToGo.value = 1;
threadToGo.notify();
}
}
} catch (InterruptedException e) {
System.out.println("Oops...");
}
}
};
}
class ThreadToGo {
int value = 1;
}
public static void main(String args[]) throws InterruptedException {
MethodOne one = new MethodOne();
Helper.instance.run(one.newThreadOne());
Helper.instance.run(one.newThreadTwo());
Helper.instance.shutdown();
}
}
- 利用volatile關(guān)鍵字
volatile修飾的變量值直接存在main memory里面,子線程對該變量的讀寫直接寫入main memory,而不是像其它變量一樣在local thread里面產(chǎn)生一份copy。volatile能保證所修飾的變量對于多個線程可見性,即只要被修改,其它線程讀到的一定是最新的值。
public class MethodThree {
private volatile ThreadToGo threadToGo = new ThreadToGo();
class ThreadToGo {
int value = 1;
}
public Runnable newThreadOne() {
final String[] inputArr = Helper.buildNoArr(52);
return new Runnable() {
private String[] arr = inputArr;
public void run() {
for (int i = 0; i < arr.length; i=i+2) {
while(threadToGo.value==2){}
Helper.print(arr[i], arr[i + 1]);
threadToGo.value=2;
}
}
};
}
public Runnable newThreadTwo() {
final String[] inputArr = Helper.buildCharArr(26);
return new Runnable() {
private String[] arr = inputArr;
public void run() {
for (int i = 0; i < arr.length; i++) {
while(threadToGo.value==1){}
Helper.print(arr[i]);
threadToGo.value=1;
}
}
};
}
public static void main(String args[]) throws InterruptedException {
MethodThree three = new MethodThree();
Helper.instance.run(three.newThreadOne());
Helper.instance.run(three.newThreadTwo());
Helper.instance.shutdown();
}
}
6、線程死鎖

死鎖