10.1 基本概念
進(jìn)程之間內(nèi)存不共享
Java中線程間堆內(nèi)存和方法區(qū)共享放吩,棧內(nèi)存獨(dú)立(很重要)御毅,方法區(qū)只有1塊根欧,堆也只有1塊,椂饲可能有多個(gè)凤粗,主線程對(duì)應(yīng)主棧,在進(jìn)程中創(chuàng)建的變量或?qū)ο蟮囊梦挥跅V薪穸梗还蚕硐蛹稹#ǘ鄠€(gè)進(jìn)程間不共享局部變量)
每個(gè)棧與每個(gè)棧之間之間互不干擾,各自執(zhí)行呆躲,這就是多線程并發(fā)异逐。
image-20200606002653497.png線程的生命周期:
NEW:創(chuàng)建
RUNNABLE:?jiǎn)?dòng)線程之后灰瞻,被喚醒之后,有被CPU調(diào)度的機(jī)會(huì)
WAITING:進(jìn)入等待集
WAITINGTIME:帶阻塞時(shí)間的等待
RUNNING:運(yùn)行態(tài)
BLOCKING:阻塞態(tài)
TERMINATED:死亡態(tài)
線程的運(yùn)行由CPU調(diào)度辅甥,每一次運(yùn)行結(jié)束的位置由程序計(jì)數(shù)器計(jì)算酝润,下一次從上一次斷開(kāi)的位置繼續(xù)。
10.2 創(chuàng)建線程的方法
- 創(chuàng)建線程的方法有很多種:
- extends Thread璃弄,重寫(xiě)run()方法
MyThread t = new MyThread()
- implements Runnable要销,重寫(xiě)run()方法。==Runnable只是一個(gè)普通接口==
Runnable r = new MyRunnable(); Thread t = new Thread(r);
- implements Callable夏块,重寫(xiě)call方法疏咐。==Callable也只是一個(gè)普通接口==
Callable c = new MyCallable(); //用FutureTask去接收Callable,F(xiàn)utureTask是一個(gè)有返回值的線程脐供,因?yàn)閏all()方法有返回值 FutureTask f = new FutureTask(c); f.run(); f.get();//get是一個(gè)阻塞性方法凳鬓,會(huì)一直獲取。也可以指定等待時(shí)間
- 傳入一個(gè)線程Thread1患民,==Thread類(lèi)實(shí)現(xiàn)了Runnable接口==
Thread t = new Thread(Thread1)
- 傳入 FutureTask缩举,==FutureTask實(shí)現(xiàn)了RunnableFuture接口,RunnableFuture繼承至Runnable==
Thread t = new Thread(new FutureTask(new Callable(){重寫(xiě)call()方法}))
- 線程池ThreadPoolExecutor匹颤,在后面介紹
- 線程組ThreadGroup
10.3 線程的常用方法
開(kāi)啟線程
//Thread類(lèi) void start(); //FutureTask類(lèi) Object run();
注意如果直接調(diào)用run()方法不會(huì)開(kāi)啟線程仅孩。
設(shè)置守護(hù)線程
//守護(hù)線程會(huì)等待到所有用戶(hù)線程結(jié)束后自動(dòng)結(jié)束 void setDeamon(true);
- 中斷,設(shè)置中斷標(biāo)志
/*設(shè)置中斷標(biāo)志并不會(huì)中斷線程印蓖,如果處于wait,sleep,join等阻塞態(tài)被中斷時(shí)辽慕,會(huì)拋出InterruptedException異常 。在catch到異常之后赦肃,中斷標(biāo)志重置為false*/ void interrupt(); Thread t ; t.interrupt();
- 檢測(cè)中斷的兩個(gè)方法
//靜態(tài)方法 Thread.interrupted();//檢測(cè)當(dāng)前線程中斷標(biāo)志位溅蛉,如果為true公浪,則返回true并重置為false。 //實(shí)例方法 void isInterrupted();//檢測(cè)中斷標(biāo)志船侧,不重置
- 阻塞線程的方法
1.void sleep(long timeMills);//掛起線程持續(xù)timeMills時(shí)間欠气,單位毫秒 2.void wait() void wait(long timeMills) //需要先獲取鎖,synchronized镜撩,然后用該鎖執(zhí)行wait()预柒,線程將自己掛起并釋放鎖 3.void join() //將調(diào)用者加到當(dāng)前線程,等待調(diào)用者執(zhí)行完畢袁梗。
- 喚醒線程宜鸯,使線程重新回到Runnable態(tài)
//喚醒線程也要先獲取鎖,synchronized,并且用和wait相同的鎖去notify void notify()//隨機(jī)喚醒單個(gè)線程 void notifyAll()//喚醒所有在等待中的線程遮怜,只會(huì)喚醒在調(diào)用notifyAll之前就處于等待的線程淋袖。
==notify造成死鎖的原因分析==
? 假如有消費(fèi)者C1和C2,生產(chǎn)者P1锯梁,C1先拿到鎖适贸,發(fā)現(xiàn)沒(méi)有產(chǎn)品,就wait涝桅,然后釋放鎖拜姿,鎖被P1拿到了,就產(chǎn)品+1冯遂,然后notify蕊肥,然后下一次爭(zhēng)搶鎖,P1搶到了蛤肌,發(fā)現(xiàn)有產(chǎn)品壁却,就wait,然后釋放鎖裸准,鎖被C1或者C2拿到了展东,消費(fèi),然后notify炒俱,釋放鎖盐肃,再一次搶奪鎖時(shí),只有消費(fèi)者搶到了权悟,則它就會(huì)wait砸王,這把鎖只要被消費(fèi)者搶到,因?yàn)闆](méi)有產(chǎn)品峦阁,就只能wait谦铃。就進(jìn)入了死鎖。即只要生產(chǎn)者連續(xù)兩次拿到鎖榔昔,必然會(huì)生產(chǎn)產(chǎn)品驹闰,然后第二次將自己掛起瘪菌,后面只要消費(fèi)者消費(fèi)了之后喚醒的不是生產(chǎn)者,就會(huì)使得所有消費(fèi)者都獲得鎖嘹朗,然后把自己掛起师妙。最終進(jìn)入死鎖。
所以結(jié)論是骡显,所有生產(chǎn)者和消費(fèi)者用notifyAll一定不會(huì)死鎖,因?yàn)檫@樣會(huì)喚醒所有掛起的線程曾掂,不存在忽略了生產(chǎn)者而造成后續(xù)的死鎖惫谤。死鎖產(chǎn)生的根本原因是生產(chǎn)者只喚醒生產(chǎn)者,消費(fèi)者只喚醒消費(fèi)者珠洗,最終生產(chǎn)者都認(rèn)為滿(mǎn)了溜歪,然后掛起;消費(fèi)者消費(fèi)完了许蓖,喚醒別的消費(fèi)者蝴猪,別的消費(fèi)者就會(huì)掛起,直到最先的消費(fèi)者被掛起膊爪。而如果都是用notifyAll自阱,就不分差異的喚醒所有
- 讓出CPU執(zhí)行權(quán)
void yield();//讓出當(dāng)前的CPU執(zhí)行權(quán),讓CPU重新調(diào)度米酬,有可能再次被調(diào)度到
- 設(shè)置線程優(yōu)先級(jí)
void setPriority();//默認(rèn)等級(jí)5沛豌,最高10,最低1赃额。優(yōu)先級(jí)越高加派,被調(diào)度到的概率越大
10.4 線程同步
- 線程同步是解決線程安全問(wèn)題的方法,主要有==synchronized==同步跳芳,==Lock==同步和==CAS非阻塞算法==同步芍锦。CPU效率依次遞增
1、synchronized關(guān)鍵字
synchronized作用于方法內(nèi)
//synchronized(lock)飞盆,lock一般是共享的資源娄琉,雖然用其他唯一的實(shí)例也可以,但是推薦用共享資源 @override public void run(){ synchronized(lock){ //do something } }
synchronized作用于方法上
1.實(shí)例方法 public synchronized void method(){} //此時(shí)的鎖為this吓歇,實(shí)例對(duì)象 2.靜態(tài)方法 public synchronized static void method(){} //此時(shí)的鎖為類(lèi)鎖车胡,class,它是唯一的
2照瘾、volatile關(guān)鍵字
- volatile只保存內(nèi)存可見(jiàn)性匈棘,它不保證線程安全
- volatile修飾的變量在發(fā)生變化時(shí),會(huì)及時(shí)通知其它線程析命,重新從主內(nèi)存中取值到工作內(nèi)存主卫。但是因?yàn)関olatile不是原子性操作逃默,因此存在線程安全。
image-20200606114713038.png
- 總線嗅探機(jī)制:線程A修改信息后在匯編指令中會(huì)加Lock前綴簇搅,修改信息通過(guò)總線被線程B嗅探到完域,(因?yàn)閏pu和內(nèi)存的交互通過(guò)I\O總線),此時(shí)B就把原來(lái)內(nèi)存空間的變量變?yōu)闊o(wú)效狀態(tài)瘩将。重新從主內(nèi)存中讀取吟税。
- volatile不保證線程安全的原因:
因?yàn)榫€程A在引擎從工作內(nèi)存中拿到值之后,讀姿现,改是分步的肠仪,這時(shí)候如果另外一個(gè)線程B已經(jīng)完成了寫(xiě)到主內(nèi)存的操作,假如寫(xiě)入的值為2备典。這時(shí)候線程A會(huì)通過(guò)嗅探機(jī)制异旧,感知到volatile修改的值的變化,會(huì)將自己工作內(nèi)存的變量舍去提佣,置位無(wú)效吮蛹,但是引擎已經(jīng)讀取了值了,并且完成了值的修改拌屏,假如修改為了1潮针。這時(shí)候,引擎就會(huì)assgin倚喂,把值存到工作內(nèi)存然低,然后再寫(xiě)到主內(nèi)存,就會(huì)將原來(lái)主內(nèi)存的值給覆蓋了务唐。
3雳攘、Lock鎖
- java.util.concurrent.locks包下的Lock是一個(gè)接口,其常用實(shí)現(xiàn)類(lèi)為ReentrantLock枫笛,效率比synchronized高吨灭,更安全
- Lock鎖也只有釋放了才能被其它線程獲取到。
- ==ReentrantLock==
//它是一個(gè)獨(dú)占鎖 new ReentrantLock(boolean fair);//fair表示是否公平 公平:先到先得 不公平:先到不一定先得刑巧,隨機(jī)喧兄,默認(rèn)是不公平
常用方法:
//ReentrantLock類(lèi) void lock();//上鎖 void unlock();//釋放鎖 boolean tryLock(long time,TimeUnit timeUnit);//嘗試獲取鎖,成功則返回true啊楚,失敗則在規(guī)定的時(shí)間內(nèi)多次嘗試 Condition newCondition();//獲取Condition對(duì)象吠冤,這個(gè)對(duì)象有await()和signal,signallAll方法,相當(dāng)于wait和notify恭理,notifyAll //Condition類(lèi) void await();//相當(dāng)于wait() void await(int timeMills);//相當(dāng)于wait(int timeMills) void signal(); void signalAll();
示例:
while (true){ if (lock.tryLock(2, TimeUnit.SECONDS)){ lock.lock; try { System.out.println("我獲取到鎖了"); }finally { lock.unlock(); break; } } }
class TaskQueue { private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); private Queue<String> queue = new LinkedList<>(); public void addTask(String s) { lock.lock(); try { queue.add(s); condition.signalAll(); } finally { lock.unlock(); } } public String getTask() { lock.lock(); try { while (queue.isEmpty()) { condition.await(); } return queue.remove(); } finally { lock.unlock(); } } }
==ReentrantReadWriteLock==
//讀寫(xiě)鎖拯辙,寫(xiě)的優(yōu)先級(jí)高于讀。 讀寫(xiě)鎖,多個(gè)線程可以同時(shí)獲得讀鎖涯保,但寫(xiě)鎖是獨(dú)占的诉濒,這樣的好處是提高并發(fā)效率。適用于多個(gè)線程讀夕春,少數(shù)線程寫(xiě)未荒。且沒(méi)有寫(xiě)的情況下,才能讀及志。這是一種悲觀鎖片排。 讀鎖和寫(xiě)鎖本質(zhì)上是同一個(gè)鎖,只是讀鎖可以共享速侈。所以在讀的過(guò)程中也要加鎖率寡,防止被寫(xiě)占有,導(dǎo)致讀的過(guò)程中數(shù)據(jù)變更引起臟讀 //示例 public class Counter { private final ReadWriteLock rwlock = new ReentrantReadWriteLock(); private final Lock rlock = rwlock.readLock(); private final Lock wlock = rwlock.writeLock(); private int[] counts = new int[10]; public void inc(int index) { wlock.lock(); // 加寫(xiě)鎖 try { counts[index] += 1; } finally { wlock.unlock(); // 釋放寫(xiě)鎖 } } public int[] get() { rlock.lock(); // 加讀鎖 try { return Arrays.copyOf(counts, counts.length); } finally { rlock.unlock(); // 釋放讀鎖 } } }
- ==StampledLock==
這是一種樂(lè)觀鎖锌畸,允許在讀的時(shí)候?qū)懹铝樱阅鼙萊eentrantReadWriteLock高
4靖避、JUC包下的原子類(lèi)
Atomic類(lèi)采用的是CAS非阻塞算法潭枣,使用的是Unsafe類(lèi)的compareAndSwap()作為基礎(chǔ)。Unsafe類(lèi)下還有很多其它方法幻捏,參考==《并發(fā)編程之美》==盆犁。
CAS是機(jī)器級(jí)別的指令,效率比加鎖的方式高篡九。
并發(fā)包分類(lèi):
- 其中LongBinaryOperator中的left就是每次accumulate運(yùn)算后的結(jié)果谐岁,第一次計(jì)算時(shí)是構(gòu)造LongAccumulator時(shí)傳入的identity,right是accumulate傳入的參數(shù)榛臼。
- LongAdder和LongAccumulator是相對(duì)于Atomic類(lèi)而言伊佃,它們類(lèi)似ThreadLocal,每個(gè)線程都有一個(gè)自己的復(fù)制變量沛善,不需要再為了競(jìng)爭(zhēng)原子資源而消耗時(shí)間航揉。
- 原子類(lèi)和鎖的不同之處在于鎖是阻塞的,只能被一個(gè)線程只有金刁,其它線程都必須阻塞等待帅涂。而原子類(lèi)不會(huì)阻塞線程,只不過(guò)同一時(shí)間只有一個(gè)線程可以操作成功尤蛮。
image-20200606121302916.png
10.5 ThreadLocal和ThreadLocalRandom
1媳友、ThreadLocal類(lèi)
- ThreadLocal<T>是本地線程變量,它是一個(gè)泛型類(lèi)产捞,可以存儲(chǔ)任意類(lèi)型醇锚。每個(gè)線程都有一個(gè)ThreadLocal的復(fù)制∨髁伲互不影響搂抒。==但是必須先在自己的虛擬機(jī)棧里set這個(gè)值==艇搀。一般定義為==static==,最后要在==finally里清除ThreadLocal==求晶。
- 常用方法
void set(); Object get(); void remove()清除
2焰雕、ThreadLocalRandom類(lèi)
- ThreadLocalRandom是多線程下使用的Random隨機(jī)數(shù)類(lèi)。
ThreadLocalRandom.current()//獲取當(dāng)前線程的ThreadLocalRandom對(duì)象芳杏。
10.6 線程池
1矩屁、基本概念
線程池和線程組:
在main方法中自定義的線程都屬于同一個(gè)線程組,同一個(gè)線程組共享變量爵赵,但是線程組使用較少吝秕。
線程池的效率高,它降低了創(chuàng)建線程的開(kāi)銷(xiāo)空幻。
線程池的關(guān)系
==主要接口視圖關(guān)系==:
Executor:線程池的頂級(jí)接口
ExecutorService:線程池的真正接口
ScheduledExecutorService:需要執(zhí)行重復(fù)任務(wù)的線程池的接口
ThreadPoolExecutor:ExecutorService的默認(rèn)實(shí)現(xiàn)類(lèi)
ScheduledThreadPooleExecutor:繼承至ThreadPoolExecturo烁峭,實(shí)現(xiàn)ScheduledExecutorService接口
如果要執(zhí)行非重復(fù)任務(wù),就轉(zhuǎn)為ExecutorServiece秕铛,如果要執(zhí)行重復(fù)任務(wù)约郁,就轉(zhuǎn)為ScheduledExecutorService。
image-20200606124629735.png
- 工具類(lèi)
==Executors==提供一些靜態(tài)工廠但两,用于創(chuàng)建不同類(lèi)型的線程池鬓梅。
線程池的一些參數(shù)
==corePoolSize==:核心線程數(shù)量
==maximumPooleSize==:最大線程數(shù)量==keepAliveTime==:線程沒(méi)有任務(wù)時(shí),最多保存多長(zhǎng)時(shí)間后會(huì)終止
2谨湘、線程池分類(lèi)
- ==newCachedThreadPool==
//創(chuàng)建一個(gè)帶有緩沖的線程池绽快,緩沖的意思是可以動(dòng)態(tài)調(diào)整線程池的核心線程數(shù)量,當(dāng)超過(guò)了默認(rèn)大小時(shí)紧阔,就擴(kuò)容坊罢。可以人為指定一個(gè)區(qū)間擅耽。 ExecutorService service = Executors.newCachedThreadPool(); //線程池活孩,無(wú)大小限制
- ==newFixedThreadPool==
//創(chuàng)建一個(gè)指定大小的線程池 ExecutorService service = Executors.newFixedThreadPool(2);
- ==newSingleThreadPoolExecutor==
//創(chuàng)建一個(gè)單線程的線程池,線程是串行執(zhí)行任務(wù) ExecutorService service = Executors.newSingleThreadExecutor() //單線程的執(zhí)行順序秫筏,先執(zhí)行t1再t2诱鞠,再t3,相當(dāng)于固定大小為1 service.execute(t1); service.execute(t2); service.execute(t3);
==newScheduledThreadPool==
//周期性的執(zhí)行任務(wù) ExecutorService service = Executors.newScheduledThreadPool(); service.schedule(t1,2, TimeUnit.SECONDS);//延遲多久執(zhí)行这敬,只執(zhí)行一次航夺,一次性任務(wù) service.scheduleAtFixedRate(t1,1,2,TimeUnit.SECONDS);//任務(wù),第一次執(zhí)行的時(shí)間崔涂,間隔多久執(zhí)行一次阳掐,時(shí)間單位 service.scheduleWithFixedDelay(t1,1,2,TimeUnit.SECONDS);//任務(wù),第一次執(zhí)行的時(shí)間,間隔多久執(zhí)行一次缭保,時(shí)間單位 service.shutdown();//關(guān)閉線程池
scheduleAtFixedRate 間隔多久就執(zhí)行一次汛闸,從任務(wù)一開(kāi)始就計(jì)時(shí),不管上一次任務(wù)是否執(zhí)行完畢艺骂。但是如果該任務(wù)的執(zhí)行時(shí)間大于周期诸老,那么后續(xù)任務(wù)可能會(huì)延遲開(kāi)始,但是不會(huì)并發(fā)執(zhí)行 scheduleWithFixedDelay 間隔多久執(zhí)行一次钳恕,指的是等上一次任務(wù)執(zhí)行完畢别伏,再間隔多久,執(zhí)行
線程池的執(zhí)行任務(wù)方法:
//submit Object submit(Callable<T> callable);//適用于有返回值的線程忧额,雖然也可以接收Runnable厘肮,這是返回的是null,但是為了區(qū)分睦番,最好接收Callable类茂。 //execute void execute(Runnable runnable);//執(zhí)行無(wú)返回值的線程