Thread是線程,要學習線程,必須先知道進程。
百度進程:進程(Process)是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操作系統結構的基礎。
我理解的進程:進程是電腦或者手機上運行的一個應用。
百度線程:線程,有時被稱爲輕量級進程(Lightweight Process,LWP),是程序執行流的最小單元。
我理解的線程:一個應用程序中的執行路徑,或者說是應用中的某一項任務。
二、主要內容
1、Thread類的定義有兩種方式
繼承Thread類
public class MyThread extends Thread {
@Override
public void run() {
super.run();
}
}
實現Runnable接口
public class MyRunnable implements Runnable {
@Override
public void run() {
}
}
2、自定義線程不共享數據與共享數據。
不共享數據
給線程定義一個成員變量並初始化,每次創建線程對象,該成員變量都會被初始化,在每個線程中,都是各自的成員變量。
public class MyThread extends Thread {
private int count = 5;
public MyThread(String name){
super();
this.setName(name);
}
@Override
public void run() {
super.run();
while(count>0){
count--;
System.out.println("由"+this.getName()+"該線程計算的count的結果是:"+count);
}
}
}
測試代碼public static void main(String[] args) {
MyThread t1 = new MyThread("A");
MyThread t2 = new MyThread("B");
MyThread t3 = new MyThread("C");
MyThread t4 = new MyThread("D");
MyThread t5 = new MyThread("E");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
測試結果
由A該線程計算的count的結果是:4
由B該線程計算的count的結果是:4
由B該線程計算的count的結果是:3
由B該線程計算的count的結果是:2
由B該線程計算的count的結果是:1
由A該線程計算的count的結果是:3
由B該線程計算的count的結果是:0
由A該線程計算的count的結果是:2
由A該線程計算的count的結果是:1
由A該線程計算的count的結果是:0
由C該線程計算的count的結果是:4
由C該線程計算的count的結果是:3
由C該線程計算的count的結果是:2
由E該線程計算的count的結果是:4
由C該線程計算的count的結果是:1
由D該線程計算的count的結果是:4
由D該線程計算的count的結果是:3
由D該線程計算的count的結果是:2
由E該線程計算的count的結果是:3
由D該線程計算的count的結果是:1
由C該線程計算的count的結果是:0
由D該線程計算的count的結果是:0
由E該線程計算的count的結果是:2
由E該線程計算的count的結果是:1
由E該線程計算的count的結果是:0
共享數據
給該成員變量加上static的修飾符,則創建的線程都會共用該成員變量,從而達到共享數據的效果 ,但在多線程時,會出現併發的問題(不同的線程在不同的時間訪問到的數據是相同)。
public class MyThreadTwo extends Thread {
private static int count = 5;
public MyThreadTwo(String name){
super();
this.setName(name);
}
@Override
public void run() {
super.run();
count--;
System.out.println("由"+Thread.currentThread().getName()+"線程計算count的結果是:"+count);
}
}
測試結果
由B線程計算count的結果是:3
由C線程計算count的結果是:2
由A線程計算count的結果是:3
由E線程計算count的結果是:1
由D線程計算count的結果是:0
還有一種共享數據的方法,在創建線程對象時,使用下面的構造方法
MyThreadOne mThread = new MyThreadOne();
Thread t1 = new Thread(mThread,"A");
Thread t2 = new Thread(mThread,"B");
Thread t3 = new Thread(mThread,"C");
Thread t4 = new Thread(mThread,"D");
Thread t5 = new Thread(mThread,"E");
MyThreadOne的代碼
public class MyThreadOne extends Thread{
private int count = 5;
@Override
public void run() {
super.run();
count--;
System.out.println("由"+Thread.currentThread().getName()+"線程計算count的結果是:"+count);
}
}
測試結果
由A線程計算count的結果是:4
由B線程計算count的結果是:2
由C線程計算count的結果是:2
由D線程計算count的結果是:0
由E線程計算count的結果是:1
同樣會出現併發問題
解決多線程併發問題
使用synchronized關鍵字加鎖
使用第一種方式共享數據,解決併發問題,需要使用同步代碼塊,這裏要鎖的對象必須是唯一的。
synchronized (MyThreadTwo.class) {
count--;
System.out.println("由" + Thread.currentThread().getName() + "線程計算count的結果是:" + count);
}
測試結果
由A線程計算count的結果是:4
由E線程計算count的結果是:3
由D線程計算count的結果是:2
由B線程計算count的結果是:1
由C線程計算count的結果是:0
使用第二種方式共享數,解決併發問題,可以在run方法前面加上synchronized關鍵字進行加鎖操作。
@Override
synchronized public void run() {
super.run();
count--;
System.out.println("由"+Thread.currentThread().getName()+"線程計算count的結果是:"+count);
}
3、線程的幾個常用的方法
3.1、靜態方法currentThread()。
獲取當前程序所在的線程對象。
3.2、isAlive()
判斷線程是否是活躍狀態。
活動狀態:已經啓動且尚未結束。
3.3、靜態方法sleep(long time).
讓當前運行的線程休眠time毫秒
3.4、getId()
獲取線程的唯一標識。
4、停止線程
4.1、停止線程的幾種方式
1、線程正常停止,即run()方法中的程序執行完畢。
2、使用stop()方法,強制線程停止,但不安全,不推薦使用。
使用stop()停止線程的弊端:
(1)、可能使一些釋放資源的 工作得不到完成 。
(2)、對鎖的對象解鎖,導致數據得不到同步的處理。
3、使用interrupt()方法中斷線程。
當使用該方法停止線程時,如果進入了停止狀態,可以拋一個InterruptedException異常,達到停止線程的目的。
當線程在休眠狀態時,調用該方法,會進入catch語句,並會清除停止狀態。
當線程在運行狀態時,調用該方法,是不會進入catch語句。
4.2、判斷線程是否是停止狀態
interrupted()線程的靜態方法,測試當前線程是否已經中斷,線程的中斷狀態由該方法清除。
isInterrupted(),測試線程是否已經中斷,不清除中斷狀態。
8、守護線程
8.1、調用線程的serDecmon(boolean b)方法,可以設置該線程是不是守護線程。
8.2、當進行中不存在非守護線程時,守護線程也將銷燬。
9、給方法加關鍵字synchronized同步
9.1、方法中的私有變量是不存在線程安全問題
9.2、多個線程訪問同一對象的成員變量纔會出現線程安全問題
9.3、在方法加鎖,鎖的是對象,所以不同線程訪問不同對象的成員變量,仍然是異步。
9.4、A線程已經持有object對象的鎖了,B線程可以調用object對象中的非同步方法。
9.5、當線程已經持有object對象的鎖了,可以繼續調用該對象中的其他同步方法。
9.6、當一個線程持有object對象的鎖時,另外一線程想訪問該對象的另外一個同步方法時,必須等待第一個線程釋放鎖之後,才能訪問。
9.7、當線程已經持有object對象的鎖時,再訪問其他該對象的同步方法時,是不需要等待該對象釋放鎖的。
9.8、當一個線程執行代碼發生異常時,它所持有的鎖也會釋放掉。
9.9、同步不具有繼承性的,如果子類需要同步,必須自己加上synchronized關鍵字。
10、使用關鍵字synchronized同步代碼塊
10.1、同步方法的缺點:當方法有耗時操作時,其它線程必須等待當前線程執行完畢之後釋放鎖之後才能執行,比較浪費時間。
10.2、在synchronized(){}內的代碼同步,其它的就不同步。
10.3、使用同一對象的鎖,它們之間是同步的。
10.4、使用synchronized(this)時,鎖對象是當前對象。
10.5、鎖對象不同,代碼塊之間是異步的。
10.6、同步代碼塊在非同步方法中調用,並不能保證調用非同步方法的線程同步。
10.7、當關鍵字synchronized加在靜態方法上的時候,鎖對象是該類所對應的Class類。
10.8、使用synchronized(MyObject.class){}同步代碼塊,與使用關鍵字synchronized同步靜態方法是一樣的。
10.9、儘量避免使用String的對象加鎖,因爲String有個常量池功能,String str1="A",String str2 = "A"。str1與str2是同一對象。
10.10、互相等待對方釋放鎖,就有可能出現死鎖情況。
11、關鍵字volatile
11.1、關鍵字volatile主要作用是使變量在多線程之間可見。沒用關鍵字volatile修飾之前,變量是在私有堆棧與公有堆棧中,但是它們之間是不同步的;使用了關鍵字volatile修飾,程序會強制從公有堆棧中取值。
11.2、使用關鍵字volatile的缺點是:不支持原子性。
11.3、線程安全包含原子性和可見性。
11.4、使用原子類進行原則操作,AtomicInteger、AtomicLong...
12、線程間通信。
12.1、等待通知機制,wait()方法與notify()方法只能在同步方法或同步代碼塊中調用,wait()方法調用之後會立即釋放鎖,notify()方法調用之後不會立即釋放鎖 。
12.2、線程的生命週期,就緒狀態、可運行狀態、運行狀態、暫停狀態、銷燬狀態;就緒狀態:線程創建之後進入就緒狀態;可運行狀態:線程調用了start()方法,但未搶到CPU資源進入可運行狀態;運行狀態:線程在可運行狀態下,搶到了CPU資源進入運行狀態;暫停狀態:線程在運行狀態下,調用了suspend()/sleep()/wait()方法進入暫停狀態;銷燬狀態:當線程運行完run()方法中的代碼後進入銷燬狀態。
12.3、鎖對象有兩個隊列,就緒隊列與阻塞隊列;線程調用了wait()方法,進入阻塞隊列,線程被喚醒後,進入就緒隊列;就緒隊列中存放了將要獲得鎖的線程,阻塞隊列存放了被阻塞的線程。
12.4、喚醒所有線程notifyAll();等待N毫秒之後自動喚醒方法wait(long N),當然也可以在這之前調用notify()或notifyAll()方法喚醒。
12.5、wait()條件發生變化時,應該將if判斷改爲while判斷,再判斷一次,看條件是否發生變化。
12.6、多個生產者或者多個消費者,出現“假死”狀態(不停的喚醒同類),將notity()方法改爲notifyAll()。
12.7、可以通過管道實現線程間通信;
字節流:
PipedInputStream pis = new PipedInputStream();
PipedOutputStream pos = new PipedOutputStream();
通過pis.connect(pos)或者pos.connect(pis)連接通道;
字符流:
PipedReader pr = new PipedReader();
PipedWriter pw = new PipedWriter();
連接pr.connect(pw)或者pw.connect(pr);
將這兩個流分別傳入兩個線程中,一個用來讀,一個用來寫,從而實現線程間通信。
13、線程加入join()
13.1、使線程正常執行run裏面的代碼,無限阻塞當前線程知道run方法執行完畢。
13.2、有個重載的join(long time)方法,阻塞時間有限制,如果到了阻塞時間且線程還未運行完,會執行後邊的代碼。
13.3、方法join(long time)與sleep(long time)的區別,join(long time)內部使用的是wait(long time),所以join(long time)方法釋放鎖,而sleep(long time)不釋放鎖。
14、類ThreadLocal
14.1、與線程綁定,存儲綁定線程的私有值。
14.2、具有線程變量的隔離性。
14.3、第一次使用該類對象的get方法獲取值返回都爲null。
14.4、解決第一次get總爲null問題,自定義一個類繼承ThreadLocal,重寫initialValue(),返回一個任意對象,則它就爲默認獲取到的對象。
15、類InheritableThreadLocal
15.1、可以在子線程中繼承父線程中的值。
15.2、繼承父線程中的值且修改。繼承同14.4,修改重寫childValue()方法,返回parentValue+要添加的值。
16、類Lock
16.1、使用方式:
創建對象 Lock lock = new ReentrantLock();
上鎖 lock.lock();
同步代碼塊;
釋放鎖 lock.unlock();
16.2、類Condition,等待與喚醒
使用Condition讓線程等待
創建Lock對象 Lock lock = new ReentrantLock();
創建Condition對象 Condition condition = new Condition();
上鎖 lock.lock();
同步代碼塊;
等待 condition.await();
同步代碼塊;
釋放鎖 lock.unlock();
使用Condition喚醒正在等待的線程。將上邊的代碼中的
等待 condition.await(); 替換爲
喚醒 condition.signal();
與Object的方法進行對比
Object類的wait()方法相當於Condition類的await()方法;
Object類的wait(long t)方法相當於Condition類的await(long t)方法;
Object類的notify()方法相當於Condition類的signal()方法;
Object類的notifyAll()方法相當於Condition類的signalAll()方法;
16.3、公平鎖與非公平鎖
構造方法 ReentrantLock(boolean fair),參數就是表示是否是公平鎖。
公平鎖:誰先運行誰先獲得鎖
非公平鎖:隨機獲得鎖
16.4、ReentrantLock幾個常用的方法
getHoldCount():查詢當前線程持有該鎖的個數;
getQueueLength():獲取等待該鎖的個數;
getWaitQueueLenght():獲取等待喚醒線程的個數;
hasQueueThread():查詢指定線程是否等待獲取該鎖;
hasQueueThreads():查詢是否有線程等待獲取該鎖;
hasWaiters():查詢是否有線程等待喚醒;
isFair():判斷是不是公平鎖;
isHeldByThread():查詢當前線程是否持有此鎖;
isLocked():查詢此鎖是否被任意線程鎖持有;
lockInterruptibly():如果當前線程未被中斷,則獲取該鎖,如果中斷則拋出異常。
tryLock():在沒有線程持有該鎖的情況下,獲得該鎖。
tryLock(long timeout,TimeUnit tu):如果在給定時間內沒有被其它線程持有該鎖且沒有被中斷,則獲取該鎖;參1:時長、參2:單位(枚舉值)。
16.5、Condition常用的幾個方法
awaitUninterruptibly():在不管什麼情況下,讓線程在接收到喚醒之前一直處於等待狀態。
awaitUntial(Date date):在時間到來之時,自動喚醒,也可以在時間到來之前,主動喚醒。
16.6、類ReenrantReadWriteLock(讀寫鎖)
讀寫鎖有兩個,一個是讀鎖也可叫共享鎖,一個是寫鎖也叫排他鎖。
讀鎖的使用:
創建對象 ReenrantReadWriteLock lock = ReenrantReadWriteLock();
獲取鎖 lock.readLock.lock();
要鎖的代碼塊
釋放讀鎖 lock.readLock.unlock();
讀鎖與讀鎖之間是不互斥的。
寫鎖的使用:
創建對象 ReenrantReadWriteLock lock = ReenrantReadWriteLock();
獲取鎖 lock.writeLock.lock();
要鎖的代碼塊
釋放讀鎖 lock.writeLock.unlock();
寫鎖與寫鎖之間是互斥的。
讀鎖與寫鎖之間是互斥的。