更新時間:2021-05-17 15:54:59 來源:動力節點 瀏覽1976次
小編相信所有的東西都是以實際使用價值而去學習的,沒有實際價值的學習,學了沒用,沒用就不會學的好。
多線程也是一樣,以前學習Java并沒有覺得多線程有多了不起,不用多線程我一樣可以開發,但是做的久了你就會發現,一些東西必須用多線程去解決。
明白并發編程是通過cpu調度算法,讓用戶看上去同時執行,實際上從cpu操作層面不是真正的同時。
多線程安全問題原因是在cpu執行多線程時,在執行的過程中可能隨時切換到其他的線程上執行。
用戶的線程類只須繼承Thread類并重寫其run()方法即可,通過調用用戶線程類的start()方法即可啟動用戶線程
class MyThread extends Thread{
public void run(){
}
}
public class TestThread{
public static void main(String[] args){
MyThread thread = new MyThread();//創建用戶線程對象
thread.start();//啟動用戶線程
thread.run();//主線程調用用戶線程對象的run()方法
}
}
當使用Thread(Runnable thread)方式創建線程對象時,須為該方法傳遞一個實現了Runnable接口的對象,這樣創建的線程將調用實現Runnable接口的對象的run()方法
public class TestThread{
public static void main(String[] args){
Mythread mt = new Mythread();
Thread t = new Thread(mt);//創建用戶線程
t.start();//啟動用戶線程
}
}
class Mythread implements Runnable{
public void run(){
}
}
至于哪個好,不用說肯定是后者好,因為實現接口的方式比繼承類的方式更靈活,也能減少程序之間的耦合度,面向接口編程也是設計模式6大原則的核心。
指在并發的情況之下,該代碼經過多線程使用,線程的調度順序不影響任何結果。
線程安全也是有幾個級別的:
(1)不可變
像String、Integer、Long這些,都是final類型的類,任何一個線程都改變不了它們的值,要改變除非新創建一個,因此這些不可變對象不需要任何同步手段就可以直接在多線程環境下使用
(2)絕對線程安全
不管運行時環境如何,調用者都不需要額外的同步措施。要做到這一點通常需要付出許多額外的代價,Java中標注自己是線程安全的類,實際上絕大多數都不是線程安全的,不過絕對線程安全的類,Java中也有,比方說CopyOnWriteArrayList、CopyOnWriteArraySet
(3)相對線程安全
相對線程安全也就是我們通常意義上所說的線程安全,像Vector這種,add、remove方法都是原子操作,不會被打斷,但也僅限于此,如果有個線程在遍歷某個Vector、有個線程同時在add這個Vector,99%的情況下都會出現ConcurrentModificationException,也就是fail-fast機制。
(4)線程非安全
這個就沒什么好說的了,ArrayList、LinkedList、HashMap等都是線程非安全的類
死鎖:學習操作系統時給的定義:死鎖是指兩個或兩個以上的進程在執行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處于死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱為死鎖進程。
樂觀鎖:就像它的名字一樣,對于并發間操作產生的線程安全問題持樂觀狀態,樂觀鎖認為競爭不總是會發生,因此它不需要持有鎖,將比較-設置這兩個動作作為一個原子操作嘗試去修改內存中的變量,如果失敗則表示發生沖突,那么就應該有相應的重試邏輯。
悲觀鎖:還是像它的名字一樣,對于并發間操作產生的線程安全問題持悲觀狀態,悲觀鎖認為競爭總是會發生,因此每次對某資源進行操作時,都會持有一個獨占的鎖,就像synchronized,不管三七二十一,直接上了鎖就操作資源了。
(1)線程間的通信
多個線程處理同一個資源,需要線程間通信解決線程對資源的占用,避免對同一資源爭奪。及引入等待喚醒機制(wait(),notify())
(a)wait()方法:線程調用wait()方法,釋放它對鎖的擁有權,然后等待另外的線程來通知它(通知的方式是notify()或者notifyAll()方法),這樣它才能重新獲得鎖的擁有權和恢復執行。
要確保調用wait()方法的時候擁有鎖,即,wait()方法的調用必須放在synchronized方法或synchronized塊中。
(b)notify()方法:notify()方法會喚醒一個等待當前對象的鎖的線程。喚醒在此對象監視器上等待的單個線程。
(c)notifAll()方法:notifyAll()方法會喚醒在此對象監視器上等待的所有線程。
(2)兩個線程之間共享數據:網上給出的兩種方式
方式一:當每個線程執行的代碼相同時,可以使用同一個Runnable對象
public class MultiThreadShareData {
public static void main(String[] args) {
ShareData task = new ShareData(); //一個類實現了Runnable接口
for(int i = 0; i < 4; i ++) { //四個線程來賣票
new Thread(task).start();
}
}
}
class ShareData implements Runnable {
private int data = 100;
@Override
public void run() { //賣票,每次一個線程進來,先判斷票數是否大于0
// while(data > 0) {
synchronized(this) {
if(data > 0) {
System.out.println(Thread.currentThread().getName() + ": " + data);
data--;
}
}
// }
}
}
方式二:若每個線程執行任務不同,可以將兩個任務方法放到一個類中,然后將data也放在這個類中,然后傳到不同的Runnable中,即可完成數據的共享
public class MultiThreadShareData {
public static void main(String[] args) {
ShareData task = new ShareData(); //公共數據和任務放在task中
for(int i = 0; i < 2; i ++) { //開啟兩個線程增加data
new Thread(new Runnable() {
@Override
public void run() {
task.increment();
}
}).start();
}
for(int i = 0; i < 2; i ++) { //開啟兩個線程減少data
new Thread(new Runnable() {
@Override
public void run() {
task.decrement();
}
}).start();
}
}
}
class ShareData /*implements Runnable*/ {
private int data = 0;
public synchronized void increment() { //增加data
System.out.println(Thread.currentThread().getName() + ": before : " + data);
data++;
System.out.println(Thread.currentThread().getName() + ": after : " + data);
}
public synchronized void decrement() { //減少data
System.out.println(Thread.currentThread().getName() + ": before : " + data);
data--;
System.out.println(Thread.currentThread().getName() + ": after : " + data);
}
}
本地線程:ThreadLocal
作用:避免頻繁地創建和銷毀線程,達到線程對象的重用。另外,使用線程池還可以根據項目靈活地控制并發的數目。
1)ThreadPoolExecutor類是線程池中最核心的一個類,它提供了四個構造方法。
public class ThreadPoolExecutor extends AbstractExecutorService {
/**
*corePoolSize:核心池的大小
*maximumPoolSize:線程池最大線程數
*keepAliveTime:表示線程沒有任務執行時最多保持多久時間會終止
*unit:參數keepAliveTime的時間單位,有7種取值,在TimeUnit類中有7種靜態屬性
* TimeUnit.DAYS; //天
* TimeUnit.HOURS; //小時
* TimeUnit.MINUTES; //分鐘
* TimeUnit.SECONDS; //秒
* TimeUnit.MILLISECONDS; //毫秒
* TimeUnit.MICROSECONDS; //微妙
* TimeUnit.NANOSECONDS; //納秒
*workQueue:一個阻塞隊列,用來存儲等待執行的任務
* ArrayBlockingQueue;
* LinkedBlockingQueue;
* SynchronousQueue;
*threadFactory:線程工廠,主要用來創建線程
*handler:表示當拒絕處理任務時的策略,有以下四種取值
* ThreadPoolExecutor.AbortPolicy:丟棄任務并拋出RejectedExecutionException異常。
* ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。
* ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然后重新嘗試執行任務(重復此過程)
* ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務
*/
.....
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
...
}
2)ThreadPoolExecutor的其他方法
a)execute()方法實際上是Executor中聲明的方法,在ThreadPoolExecutor進行了具體的實現,這個方法是ThreadPoolExecutor的核心方法,通過這個方法可以向線程池提交一個任務,交由線程池去執行。
b)submit()方法是在ExecutorService中聲明的方法,在AbstractExecutorService就已經有了具體的實現,在ThreadPoolExecutor中并沒有對其進行重寫,這個方法也是用來向線程池提交任務的,但是它和execute()方法不同,它能夠返回任務執行的結果,去看submit()方法的實現,會發現它實際上還是調用的execute()方法,只不過它利用了Future來獲取任務執行結果
c)shutdown()和shutdownNow()是用來關閉線程池的。
d)還有很多其他的方法:比如:getQueue()、getPoolSize()、getActiveCount()、getCompletedTaskCount()等獲取與線程池相關屬性的方法,有興趣的朋友可以自行查閱API。
使用時,并不提倡直接使用ThreadPoolExcutor,而是使用Executors類中的幾個靜態方法來創建線程池,即
Executors.newCachedThreadPool(int Integer.MAX_VALUE ); //創建一個緩沖池,緩沖池容量大小為
Executors.newSingleThreadExecutor(); //創建容量為1的緩沖池
Executors.newFixedThreadPool(); //創建固定容量大小的緩沖池
使用示例:
public class ThreadPoolTest{
public static void main(String[] args){
// 創建一個容量為5的線程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
// 向線程池提交一個任務(其實就是通過線程池來啟動一個線程)
for( int i = 0;i<15;i++){
executorService.execute(new TestRunnable());
system.out.println("******************");
}
executorService.shotdown();
}
}
class TestRunnable extends Thread{
@override
public void run(){
try{
Thread.sleep(1000*6);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
1)如果你提交任務時,線程池隊列已滿,這時會發生什么
如果你使用的LinkedBlockingQueue,也就是無界隊列的話,沒關系,繼續添加任務到阻塞隊列中等待執行,因為LinkedBlockingQueue可以近乎認為是一個無窮大的隊列,可以無限存放任務;如果你使用的是有界隊列比方說ArrayBlockingQueue的話,任務首先會被添加到ArrayBlockingQueue中,ArrayBlockingQueue滿了,則會使用拒絕策略RejectedExecutionHandler處理滿了的任務,默認是AbortPolicy。
2)高并發、任務執行時間短的業務怎樣使用線程池?并發不高、任務執行時間長的業務怎樣使用線程池?并發高、業務執行時間長的業務怎樣使用線程池?這是我在并發編程網上看到的一個問題:
①高并發、任務執行時間短的業務,線程池線程數可以設置為CPU核數+1,減少線程上下文的切換
②并發不高、任務執行時間長的業務要區分開看:
③并發高、業務執行時間長,解決這種類型任務的關鍵不在于線程池而在于整體架構的設計,看看這些業務里面某些數據是否能做緩存是第一步,增加服務器是第二步,至于線程池的設置,設置參考2)。最后,業務執行時間長的問題,也可能需要分析一下,看看能不能使用中間件對任務進行拆分和解耦。
多線程的實現和啟動
callable與runable區別
syncrhoized,reentrantLock各自特點和比對
線程池
future異步方式獲取執行結果
concurrent包
lock
線程協作:
以上就是動力節點小編介紹的"Java多線程并發編程",希望對大家有幫助,如有疑問,請在線咨詢,有專業老師隨時為您服務。
0基礎 0學費 15天面授
有基礎 直達就業
業余時間 高薪轉行
工作1~3年,加薪神器
工作3~5年,晉升架構
提交申請后,顧問老師會電話與您溝通安排學習