內(nèi)容
1.線程與進(jìn)程
2.線程介紹與使用
關(guān)于線程內(nèi)容也可參看我的另外幾篇博客
http://www.lxweimin.com/p/cb7e5dbe7968
http://www.lxweimin.com/p/88d5e3bc726c
http://www.lxweimin.com/p/2783773aa0d8
http://www.lxweimin.com/p/cbc22e2f3be1
http://www.lxweimin.com/p/84756fb84571
一.線程與進(jìn)程
1.什么是進(jìn)程
進(jìn)程,就是正在運行/執(zhí)行的一個程序。進(jìn)程是用于管理其所有的資源的,不進(jìn)行實際的任務(wù)。
2.什么是線程
剛才說了,進(jìn)程不完成具體的任務(wù),那么具體任務(wù)由誰來完成呢?沒錯,就是線程!
線程就是完成具體任務(wù)的。一個進(jìn)程可以有多個線程。
3.實例說明
比如打開QQ,就是打開一個進(jìn)程。但是我可以同時進(jìn)行聊天、視頻通話和刷QQ空間這三個任務(wù),每一個任務(wù)都由一個特定的線程進(jìn)行
在Java中,我們寫的程序運行起來就是一個進(jìn)程。
二.線程介紹與使用
1.主線程
主線程:就類似主路。main方法里面的代碼是在主線程里面執(zhí)行的。
還有手機什么都不動的時候,會展現(xiàn)出一個主界面,這個展現(xiàn)主界面的就是一個主線程。也就是說
①在Java里面:main方法里面的代碼,就在主線程中跑
②Android/iOS是 :啟動程序看到的UI界面就是UI主線程
2.子線程
子線程:除了主線程之外的都是子線程
3.多線程的作用
在主線程里面,任務(wù)的執(zhí)行順序是從上至下的。如果其中一個任務(wù)需要花費大量時間(比如下載一個很大的數(shù)據(jù)),那么這個任務(wù)后面的任務(wù)就會被阻塞(就類似堵車了),必須等這個任務(wù)結(jié)束才能被執(zhí)行。用戶的體驗效果不好。這個時候就需要將耗時的任務(wù)放在另外一個不在主線程里面執(zhí)行的路徑(類似走小路,這就是子線程)
4.注意點
①主程序就是用來分配/調(diào)配的。
②不管是主線程還是子線程,它都有自己獨立的內(nèi)存空間,執(zhí)行路徑,生命周期
③Thread是管理線程的一個類,它繼承Runnable接口
Thread.currentThread()可以獲取當(dāng)前線程的信息,這個方法也是很常用的
5.如何開啟一個線程
1.寫一個類繼承Thread
①創(chuàng)建類繼承Thread
重寫父類的run方法,把具體執(zhí)行的任務(wù)放在run方法里面
(其實run方法也可以通過對象.run()調(diào)用,但是如果這樣調(diào)用的話,程序還是在主線程里面執(zhí)行。這樣就沒啥意義了。所以不要這樣做。start調(diào)用的話,系統(tǒng)會自動將這個任務(wù)放在隊列中,等待調(diào)度,也就是等待操作系統(tǒng)調(diào)用。所以還是要用start方法去使用線程)
②創(chuàng)建類的對象
③調(diào)用start方法開始執(zhí)行
2.寫一個類實現(xiàn)Runnable接口
①創(chuàng)建一個類實現(xiàn)Runnable接口(這個類只是一個任務(wù),并不能直接開啟線程)
②創(chuàng)建任務(wù)類(也就是實現(xiàn)Runnable接口的那個類)的對象
③創(chuàng)建Thread類的對象(Thread類可以創(chuàng)建線程,我們只需要將自己的任務(wù)和Thread對象關(guān)聯(lián)起來)
④調(diào)用start啟動線程
6.繼承Thread來使用線程
注意使用如何使用線程的構(gòu)造方法以及getName方法的使用
import java.io.*;
import java.util.*;
public class 測試程序{
public static void main(String[] args){
System.out.println("main方法"+Thread.currentThread());
//2.創(chuàng)建具體的對象
TestThread testThread = new TestThread("子線程");
//3.啟動線程
testThread.start();
}
}
//1.創(chuàng)建類繼承Thread
class TestThread extends Thread{
public TestThread(String s){
super(s);
}
//執(zhí)行的任務(wù)在run方法里
public void run() {
//下面是線程需要執(zhí)行的任務(wù)
System.out.println("TestThread"+getName());
for(int i = 0;i < 100;i++) {
System.out.println(i+1);
}
}
}
輸出
main方法Thread[main,5,main]
TestThread子線程
1
2
(后面的省略)
4.注意點(續(xù))
線程是通過搶占時間片來得到運行機會的,誰搶到了時間片,誰就可以運行。這個時間片是由操作系統(tǒng)來分配的,所以每一次執(zhí)行結(jié)果可能都是不一致的
如下面這段程序
import java.io.*;
import java.util.*;
public class 測試程序{
public static void main(String[] args){
System.out.println("main方法"+Thread.currentThread());
//2.創(chuàng)建具體的對象
TestThread testThread = new TestThread("子線程1");
TestThread testThread2 = new TestThread("子線程2");
//3.啟動線程
testThread.start();
testThread2.start();
}
}
//1.創(chuàng)建類繼承Thread
class TestThread extends Thread{
public TestThread(String s){
super(s);
}
//執(zhí)行的任務(wù)在run方法里
public void run() {
//下面是線程需要執(zhí)行的任務(wù)
for(int i = 0;i < 10;i++) {
System.out.println(getName()+":"+(i+1));
}
}
}
的結(jié)果是
main方法Thread[main,5,main]
子線程1:1
子線程2:1
子線程2:2
子線程1:2
子線程1:3
子線程1:4
子線程1:5
子線程1:6
子線程2:3
子線程2:4
子線程2:5
子線程2:6
子線程2:7
子線程2:8
子線程2:9
子線程1:7
子線程2:10
子線程1:8
子線程1:9
子線程1:10
但是下一次的結(jié)果就不一定了
也就是說,當(dāng)調(diào)用start方法時,這個線程會自動扔到操作系統(tǒng)的任務(wù)隊列中(線程池),至于這個任務(wù)什么時候被執(zhí)行,我們無法確定,這一點由操作系統(tǒng)來決定。
7.實現(xiàn)Runnable接口來使用線程
注意如何將任務(wù)與Thread聯(lián)系起來
import java.io.*;
import java.util.*;
public class 測試程序{
public static void main(String[] args){
System.out.println("main方法"+Thread.currentThread());
//2.創(chuàng)建具體對象
//這個其實是具體執(zhí)行的任務(wù),這個類不能直接開啟線程,必須依賴于Thread類
TestRunnable testRunnable = new TestRunnable();
//3.創(chuàng)建一個Thread對象(因為只是實現(xiàn)Runnable,沒有start方法)
//讓這個線程去執(zhí)行testRunnable的任務(wù),也就是把它與testRunnable關(guān)聯(lián)起來。這個線程名字為:子線程1
Thread thread = new Thread(testRunnable,"子線程1");
Thread thread2 = new Thread(testRunnable,"子線程2");
//不同的線程執(zhí)行相同的任務(wù),其實可以理解成不同的人開同樣的車
//4.啟動線程
thread.start();
thread2.start();
}
}
//1.創(chuàng)建一個類實現(xiàn)Runnable接口
class TestRunnable implements Runnable{
public void run() {
//這個線程需要執(zhí)行的任務(wù)
for(int i = 0;i < 10;i++) {
System.out.println(Thread.currentThread().getName()+":"+(i+1));
}
}
}
輸出結(jié)果
main方法Thread[main,5,main]
子線程1:1
子線程1:2
子線程1:3
子線程1:4
子線程1:5
子線程1:6
子線程1:7
子線程1:8
子線程1:9
子線程1:10
8.兩種方式比較
Java不能多繼承,但是可以實現(xiàn)多個接口
所以用實現(xiàn)Runnable接口的那個方式更靈活一點,更容易擴展。但是寫起來稍微麻煩一點(其實也不是很麻煩,就比第一個方法多了一步):也就是說使用實現(xiàn)Runnable接口的方式使用線程更好。
即面向接口編程,能夠松耦合。而第一種方式就不夠靈活。
9.線程的生命周期
①new一個線程,就可以使線程處于創(chuàng)建狀態(tài)
②使用start,線程就屬于就緒狀態(tài)。當(dāng)搶占到時間片的時候,線程就屬于運行狀態(tài),當(dāng)失去時間片的時候,就再處于就緒狀態(tài)。以此往返。
從就緒狀態(tài)到運行狀態(tài)是由操作系統(tǒng)來實現(xiàn)的,外部無法干預(yù)。
③當(dāng)run方法結(jié)束的時候,線程就處于死亡狀態(tài)了,這是正常結(jié)束。另外,手動讓線程暫停,比如調(diào)用該線程的stop方法(不建議使用stop,因為該方法容易導(dǎo)致死鎖),再或者線程拋出一個未捕獲的Exception或Error的時候,線程也會處于死亡狀態(tài)。
④當(dāng)線程處于運行狀態(tài)時,也有可能處于阻塞狀態(tài)
(1)同步阻塞:使用synchronized就可以使線程處于同步阻塞狀態(tài),當(dāng)鎖解開的時候,就可以再返回就緒狀態(tài)
(2)等待阻塞:使用wait()就可以使線程處于等待狀態(tài),等調(diào)用notify時,就可再返回就緒狀態(tài)
(3)其他阻塞:使用sleep(),join()也可以使線程處于阻塞狀態(tài),等sleep休眠時間到,或者join()線程執(zhí)行完畢,或者io流阻塞結(jié)束。就再返回就緒狀態(tài)
源碼關(guān)于線程狀態(tài)的描述
這里我借用翻譯工具翻譯如下
10.線程常用方法
(1)如何讓一個線程結(jié)束
①不要直接調(diào)用stop方法來結(jié)束一個線程
②好的方法應(yīng)該是:自己寫一個變量/標(biāo)識符,用來標(biāo)識線程結(jié)束的臨界點
比如
class TestThread extends Thread{
private boolean shouldStop = true;
public TestThread(String s){
super(s);
}
//執(zhí)行的任務(wù)在run方法里
public void run() {
//下面是線程需要執(zhí)行的任務(wù)
while(shouldStop) {
System.out.println("子線程");
}
}
public void terminated() {
shouldStop = false;
}
}
(2)線程禮讓和“插隊“
①yield():線程禮讓
禮讓的線程會直接進(jìn)入就緒狀態(tài),被禮讓的線程并不一定會一直執(zhí)行。如果禮讓的線程再次獲得時間片,則還會再次執(zhí)行。所以,禮讓是可能失敗的。
使用示例
import java.io.*;
import java.util.*;
public class 測試程序{
public static void main(String[] args){
TestRunnable runnable = new TestRunnable();
Thread t1 = new Thread(runnable,"奔馳");
t1.start();
for(int i = 0;i < 200;i++) {
System.out.println("主線程"+(i+1));
if(i == 20) {
Thread.yield();//禮讓
}
}
}
}
//1.創(chuàng)建一個類實現(xiàn)Runnable接口
class TestRunnable implements Runnable{
public void run() {
//這個線程需要執(zhí)行的任務(wù)
for(int i = 0;i < 200;i++) {
System.out.println(Thread.currentThread().getName()+":"+(i+1));
}
}
}
②join():插隊
可以使當(dāng)前線程阻塞,插隊的線程執(zhí)行。這個基本是成功的。
使用示例
import java.io.*;
import java.util.*;
public class 測試程序{
public static void main(String[] args){
TestRunnable runnable = new TestRunnable();
Thread t1 = new Thread(runnable,"奔馳");
t1.start();
for(int i = 0;i < 200;i++) {
if(i == 20) {
try {
t1.join();
} catch (InterruptedException e) {
// TODO 自動生成的 catch 塊
e.printStackTrace();
}//插隊
}
System.out.println("主線程"+(i+1));
}
}
}
//1.創(chuàng)建一個類實現(xiàn)Runnable接口
class TestRunnable implements Runnable{
public void run() {
//這個線程需要執(zhí)行的任務(wù)
for(int i = 0;i < 200;i++) {
System.out.println(Thread.currentThread().getName()+":"+(i+1));
}
}
}
11.多線程的優(yōu)點和缺點
目前來說:
(1)優(yōu)點:提高應(yīng)用程序的使用率
(2)缺點:如果多個線程操作同一個資源,有可能出現(xiàn)不安全。所以需要解決這種問題。
12.保證線程安全的兩種方式
①Lock 鎖
必須使用的是同一個鎖
class TestRunnable implements Runnable{
private static Lock lock = new ReentrantLock();
public void run() {
//這個線程需要執(zhí)行的任務(wù)
for(int i = 0;i < 200;i++) {
lock.lock();//加鎖
lock.unlock();//解鎖
}
}
}
但是這種方式使用起來比較麻煩
②線程同步
必須保證鎖的是同一個對象。 synchronized可以鎖代碼塊和方法。
(1)第一種,隨便弄個Object對象就可以實現(xiàn)同步
class TestRunnable implements Runnable{
private static Object obj = new Object();
public void run() {
//這個線程需要執(zhí)行的任務(wù)
for(int i = 0;i < 200;i++) {
synchronized(obj) {
//這里面放被鎖住的代碼
}
}
}
}
(2)但是一般不這樣搞。一般都是鎖this
class TestRunnable implements Runnable{
private static Object obj = new Object();
public void run() {
//這個線程需要執(zhí)行的任務(wù)
for(int i = 0;i < 200;i++) {
synchronized(this) {
}
}
}
}
(3)鎖方法
class TestRunnable implements Runnable{
private static Object obj = new Object();
public void run() {
//這個線程需要執(zhí)行的任務(wù)
for(int i = 0;i < 200;i++) {
test();
}
}
private synchronized void test() {
}
}
(4)注意點:
不管是鎖代碼塊還是方法,都應(yīng)盡量讓鎖的范圍變小。
13.線程間通信
(1)線程與線程之間有時是需要通信、交互的。一般用到以下幾個方法
①wait()讓線程等待
②notify()喚醒某個線程
③notifyAll()喚醒多個線程
(2)注意:
①這三個方法不在Thread里面,而是在Object里面
②這三個方法必須由同步監(jiān)視器來調(diào)用。(總之就是大家都在搶同一個資源)。
(3)使用示例
①要求:使用線程間通信,完成輸出1 a 2 b。。。。。26 z的操作
②掌握使用匿名內(nèi)部類創(chuàng)建線程的方法
③當(dāng)鎖代碼塊的時候,一般鎖住Object對象,從而進(jìn)行線程間通信。
④當(dāng)鎖方法的時候,是哪個對象調(diào)用這個方法就鎖哪個對象。
⑤留意:Character.toChars()這個方法
這個方法在JDK幫助文檔里是這樣說的。
JDK幫助文檔
我去查了一下UTF-16,看不懂,但是我使用起來
這三個輸出結(jié)果都是 a。所以目前來說,先不必計較。或者死記住:輸出字符的時候,使用這個方法。(雖然我現(xiàn)在還沒有查出,測試出不使用這個方法有什么危害)
import java.io.*;
import java.util.*;
public class 測試程序{
public static int state = 1;//1表示輸出數(shù)字,2表示輸出字母
public static void main(String[] args){
/* TestRunnable t = new TestRunnable();
Thread task = new Thread(t);
task.start();*/
//可以使用匿名內(nèi)部類
/* Thread task = new Thread(new Runnable() {
public void run() {
for(int i = 0;i < 20;i++) {
System.out.println(Thread.currentThread().getName()+":"+(i+1));
}
}
});*/
Object obj = new Object();
new Thread(new Runnable() {
int num = 1;
public void run(){
//輸出數(shù)字
while(true) {
synchronized(obj) {//爭奪obj資源
//判斷當(dāng)前是不是在輸出字母
if(state != 1) {
//當(dāng)前線程需要等待一下
try {
obj.wait();
} catch (InterruptedException e) {
// TODO 自動生成的 catch 塊
e.printStackTrace();
}
}
//輸出數(shù)字
System.out.println(num);
num++;
if(num > 26) {
break;
}
//喚醒當(dāng)前obj鎖上的其他等待的線程
state = 2;
obj.notify();
}
}
}
}).start();
new Thread(new Runnable() {
char alpha = 'a';
public void run() {
//輸出字母
while(true) {
synchronized(obj) {//爭奪obj資源
//判斷當(dāng)前是不是在輸出數(shù)字
if(state != 2) {
//當(dāng)前線程需要等待一下
try {
obj.wait();
} catch (InterruptedException e) {
// TODO 自動生成的 catch 塊
e.printStackTrace();
}
}
//System.out.println("程序執(zhí)行到這里了");
//輸出字母
System.out.println(Character.toChars(alpha));//Character.toChars(alpha)
alpha++;
if(alpha > 'z') {
break;
}
state = 1;
obj.notify();
}
}
}
}).start();
}
}
上面是鎖的代碼塊,可以看出上面的代碼不太簡潔。下面使用鎖方法的方式來完成同樣的功能。
要記住:使用同一對象來調(diào)用方法。
import java.io.*;
import java.util.*;
public class 測試程序{
static Data d = new Data();
public static void main(String[] args){
new Thread(new Runnable() {
public void run() {
d.printNum();
}
}).start();
new Thread(new Runnable() {
public void run() {
d.printAlpha();
}
}).start();
}
}
class Data{
int num = 1;
int alpha = 'a';
int state = 1;
//此時鎖的是當(dāng)前類的對象,哪個對象調(diào)用這個方法就鎖哪個對象
public synchronized void printNum() {
while(true) {
if(state != 1) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(num);
num++;
if(num > 26) {
break;
}
state = 2;
this.notify();
}
}
public synchronized void printAlpha(){
while(true) {
if(state != 2) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Character.toChars(alpha));
alpha++;//加的是ASCⅡ碼
if(alpha > 'z') {//注意,這里是'z',而不是26
break;
}
state = 1;
this.notify();
}
}
}
總結(jié)
還是同樣的感受:相比第一次學(xué),這一次學(xué)的更多,更透徹了。也解決了很多問題。下一步就是多寫代碼,不斷進(jìn)行鞏固!加油!!要學(xué)網(wǎng)絡(luò)編程了!!激動!!