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