并發(fā)編程的挑戰(zhàn)
上下文切換
即使是單核處理器也支持多線程執(zhí)行代碼,
CPU
通過(guò)給每個(gè)線程分配CPU
時(shí)間片來(lái)實(shí)現(xiàn)這個(gè)機(jī)制。CPU
通過(guò)時(shí)間片分配算法來(lái)循環(huán)執(zhí)行任務(wù),當(dāng)前任務(wù)執(zhí)行一個(gè)時(shí)間片后會(huì)切換到下一個(gè)任務(wù)。任務(wù)從保存到再加載的過(guò)程就是一次上下文切換
切換上下文需要一定的消耗
如何減少上下文的切換
使用最少線程:避免創(chuàng)建不需要的線程
CAS
算法:Java
的Atomic
包使用CAS
算法來(lái)更新數(shù)據(jù),而不需要加鎖。無(wú)鎖并發(fā)編程:多線程競(jìng)爭(zhēng)鎖時(shí),會(huì)引起上下文切換,所以多線程處理數(shù)據(jù)時(shí),可以用一些辦法來(lái)避免使用鎖
死鎖
避免一個(gè)線程同時(shí)獲取多個(gè)鎖。
避免一個(gè)線程在鎖內(nèi)同時(shí)占用多個(gè)資源,盡量保證每個(gè)鎖只占用一個(gè)資源。
嘗試使用定時(shí)鎖,使用
lock.tryLock(timeout)
來(lái)替代使用內(nèi)部鎖機(jī)制。對(duì)于數(shù)據(jù)庫(kù)鎖,加鎖和解鎖必須在一個(gè)數(shù)據(jù)庫(kù)連接里,否則會(huì)出現(xiàn)解鎖失敗的情況
資源限制的挑戰(zhàn)
在并發(fā)編程中,將代碼執(zhí)行速度加快的原則是將代碼中串行執(zhí)行的部分變成并發(fā)執(zhí)行,但是如果將某段串行的代碼并發(fā)執(zhí)行,因?yàn)槭芟抻谫Y源,仍然在串行執(zhí)行,這時(shí)候程序不僅不會(huì)加快執(zhí)行,反而會(huì)更慢,因?yàn)樵黾恿松舷挛那袚Q和資源調(diào)度的時(shí)間
Java 并發(fā)機(jī)制的底層實(shí)現(xiàn)原理
volatile 的應(yīng)用
volatile
保證共享變量的可見(jiàn)性volatile
是輕量級(jí)的synchronized
synchronized 的實(shí)現(xiàn)原理與應(yīng)用
對(duì)于普通同步方法,鎖是當(dāng)前實(shí)例對(duì)象。
對(duì)于靜態(tài)同步方法,鎖是當(dāng)前類的
Class
對(duì)象。對(duì)于同步方法塊,鎖是
Synchonized
括號(hào)里配置的對(duì)象。
三大特性
原子性:一個(gè)或多個(gè)操作要么全部成功,要么全部失敗。保證只有一個(gè)線程拿到鎖訪問(wèn)共享資源
可見(jiàn)性:當(dāng)一個(gè)線程對(duì)共享變量進(jìn)行修改后,其他線程可以立刻看到
有序性:程序的執(zhí)行順序會(huì)按照代碼的先后順序執(zhí)行。
實(shí)現(xiàn)原理
Java
虛擬機(jī)則是通過(guò)進(jìn)入和退出Monitor
對(duì)象來(lái)實(shí)現(xiàn)方法同步和代碼塊同步的同步代碼塊的實(shí)現(xiàn)是由
monitorenter
和monitorexit
指令完成的,其中monitorenter
指令所在的位置是同步代碼塊開(kāi)始的位置,第一個(gè)monitorexit
指令是用于正常結(jié)束同步代碼塊的指令
Java 并發(fā)編程基礎(chǔ)
線程作為操作系統(tǒng)調(diào)度的最小單元,多個(gè)線程能夠同時(shí)執(zhí)行,這將顯著提升程序性能,在多核環(huán)境中表現(xiàn)得更加明顯。
過(guò)多地創(chuàng)建線程和對(duì)線程的不當(dāng)管理也容易造成問(wèn)題。
線程簡(jiǎn)介
操作系統(tǒng)調(diào)度的最小單元是線程,也叫輕量級(jí)進(jìn)程(
Light Weight Process
),在一個(gè)進(jìn)程里可以創(chuàng)建多個(gè)線程,這些線程都擁有各自的計(jì)數(shù)器、堆棧和局部變量等屬性,并且能夠訪問(wèn)共享的內(nèi)存變量。處理器在這些線程上高速切換,讓使用者感覺(jué)到這些線程在同時(shí)執(zhí)行。
使用多線程的原因
提升處理器的使用效率
異步處理請(qǐng)求,提升系統(tǒng)響應(yīng)速度提升用戶體驗(yàn)
Java
為多線程編程提供了良好、考究并且一致的編程模型,使開(kāi)發(fā)人員能夠更加專注于問(wèn)題的解決
線程優(yōu)先級(jí)
線程優(yōu)先級(jí)的范圍從
1-10
,默認(rèn)優(yōu)先級(jí)是5
針對(duì)頻繁阻塞(休眠或者
I/O
操作)的線程需要設(shè)置較高優(yōu)先級(jí),而偏重計(jì)算(需要較多CPU
時(shí)間或者偏運(yùn)算)的線程則設(shè)置較低的優(yōu)先級(jí),確保處理器不會(huì)被獨(dú)占。
線程優(yōu)先級(jí)不能作為程序正確性的依賴
線程的狀態(tài)
測(cè)試.java
public class ThreadTest { public static void main(String[] args) { new Thread(new TimeWaiting(), "超時(shí)等待線程").start(); new Thread(new Waiting(), "等待線程").start(); // 使用兩個(gè) Blocked 線程,一個(gè)獲取鎖成功,另一個(gè)被阻塞 new Thread(new Blocked(), "阻塞線程1").start(); new Thread(new Blocked(), "阻塞線程2").start(); } // 該線程不斷地進(jìn)行睡眠 static class TimeWaiting implements Runnable { private static final Logger logger = LoggerFactory.getLogger(TimeWaiting.class); @Override public void run() { while (true) { logger.info(Thread.currentThread().toString()); second(100); } } } // 該線程在 Waiting.class 實(shí)例上等待 static class Waiting implements Runnable { private static final Logger logger = LoggerFactory.getLogger(Waiting.class); @Override public void run() { while (true) { synchronized (Waiting.class) { try { logger.info(Thread.currentThread().toString()); Waiting.class.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } // 該線程在 Blocked.class 實(shí)例上加鎖后,不會(huì)釋放該鎖 static class Blocked implements Runnable { private static final Logger logger = LoggerFactory.getLogger(Blocked.class); public void run() { synchronized (Blocked.class) { while (true) { logger.info(Thread.currentThread().toString()); second(100); } } } } public static final void second(long seconds) { try { TimeUnit.SECONDS.sleep(seconds); } catch (InterruptedException e) { } }}
運(yùn)行結(jié)果
[超時(shí)等待線程] INFO util.thread.ThreadTest$TimeWaiting - Thread[超時(shí)等待線程,5,main] [等待線程] INFO util.thread.ThreadTest$Waiting - Thread[等待線程,5,main] [阻塞線程1] INFO util.thread.ThreadTest$Blocked - Thread[阻塞線程1,5,main]
Jstack
查看線程信息
// 線程被阻塞 "阻塞???程2" #14 prio=5 os_prio=0 tid=0x00000000282d2800 nid=0x231a0 waiting for monitor entry [0x0000000028f0f000] java.lang.Thread.State: BLOCKED (on object monitor) at util.thread.ThreadTest$Blocked.run(ThreadTest.java:61) - waiting to lock <0x00000007167efbb8> (a java.lang.Class for util.thread.ThreadTest$Blocked) at java.lang.Thread.run(Thread.java:748) // 線程獲取到了 Blocked.class 的鎖 "阻塞線程1" #13 prio=5 os_prio=0 tid=0x00000000282d2000 nid=0x25670 waiting on condition [0x0000000028e0f000] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method) at java.lang.Thread.sleep(Thread.java:340) at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386) at util.thread.ThreadTest.second(ThreadTest.java:70) at util.thread.ThreadTest$Blocked.run(ThreadTest.java:62) - locked <0x00000007167efbb8> (a java.lang.Class for util.thread.ThreadTest$Blocked) at java.lang.Thread.run(Thread.java:748) // 等待線程 線程在 Waiting 實(shí)例上等待 "等待線程" #12 prio=5 os_prio=0 tid=0x00000000282d1000 nid=0x24514 in Object.wait() [0x0000000028d0e000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000007167ecd70> (a java.lang.Class for util.thread.ThreadTest$Waiting) at java.lang.Object.wait(Object.java:502) at util.thread.ThreadTest$Waiting.run(ThreadTest.java:45) - locked <0x00000007167ecd70> (a java.lang.Class for util.thread.ThreadTest$Waiting) at java.lang.Thread.run(Thread.java:748) // 超時(shí)等待 "超時(shí)等待線程" #11 prio=5 os_prio=0 tid=0x00000000282cf800 nid=0x254d8 waiting on condition [0x0000000028c0e000] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method) at java.lang.Thread.sleep(Thread.java:340) at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386) at util.thread.ThreadTest.second(ThreadTest.java:70) at util.thread.ThreadTest$TimeWaiting.run(ThreadTest.java:30) at java.lang.Thread.run(Thread.java:748)
線程狀態(tài)變遷
線程創(chuàng)建之后,調(diào)用
start()
方法開(kāi)始運(yùn)行。當(dāng)線程執(zhí)行wait()
方法之后,線程進(jìn)入等待狀態(tài)。進(jìn)入等待狀態(tài)的線程需要依靠其他線程的通知才能夠返回到運(yùn)行狀態(tài),而超時(shí)等待狀態(tài)相當(dāng)于在等待狀態(tài)的基礎(chǔ)上增加了超時(shí)限制,也就是超時(shí)時(shí)間到達(dá)時(shí)將會(huì)返回到運(yùn)行狀態(tài)。當(dāng)線程調(diào)用同步方法時(shí),在沒(méi)有獲取到鎖的情況下,線程將會(huì)進(jìn)入到阻塞狀態(tài)。線程在執(zhí)行Runnable
的run()
方法之后將會(huì)進(jìn)入到終止?fàn)顟B(tài)。Java
將操作系統(tǒng)中的運(yùn)行和就緒兩個(gè)狀態(tài)合并稱為運(yùn)行狀態(tài)。阻塞狀態(tài)是線程阻塞在進(jìn)入
synchronized
關(guān)鍵字修飾的方法或代碼塊(獲取鎖)時(shí)的狀態(tài),但是阻塞在java.concurrent
包中Lock
接口的線程狀態(tài)卻是等待狀態(tài),因?yàn)?java.concurrent
包中Lock
接口對(duì)于阻塞的實(shí)現(xiàn)均使用了LockSupport
類中的相關(guān)方法。
Daemon 線程
Daemon
屬性需要在啟動(dòng)線程之前設(shè)置,不能在啟動(dòng)線程之后設(shè)置。Daemon
線程被用作完成支持性工作,但是在Java
虛擬機(jī)退出時(shí)Daemon
線程中的finally
塊并不一定會(huì)執(zhí)行。
啟動(dòng)和終止線程
構(gòu)造線程
構(gòu)造一個(gè)線程對(duì)象
提供線程所屬的線程組、線程優(yōu)先級(jí)、是否是
Daemon
等屬性
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {}
一個(gè)新構(gòu)造的線程對(duì)象是由其
parent
線程來(lái)進(jìn)行空間分配的,而child
線程繼承了parent
是否為Daemon
、優(yōu)先級(jí)和加載資源的contextClassLoader
以及可繼承的ThreadLocal
,同時(shí)還會(huì)分配一個(gè)唯一的ID
來(lái)標(biāo)識(shí)這個(gè)child
線程。至此,一個(gè)能夠運(yùn)行的線程對(duì)象就初始化好了,在堆內(nèi)存中等待著運(yùn)行。
啟動(dòng)線程
當(dāng)前線程(即
parent
線程)同步告知Java
虛擬機(jī),只要線程規(guī)劃器空閑,應(yīng)立即啟動(dòng)調(diào)用start()
方法的線程
new Thread(new TimeWaiting(), "超時(shí)等待線程").start();
理解中斷
線程的一個(gè)標(biāo)識(shí)位屬性,它表示一個(gè)運(yùn)行中的線程是否被其他線程進(jìn)行了中斷操作。中斷好比其他線程對(duì)該線程打了個(gè)招呼,其他線程通過(guò)調(diào)用該線程的
interrupt()
方法對(duì)其進(jìn)行中斷操作。
過(guò)期的 suspend()、resume()和 stop()
suspend()
、resume()
和stop()
方法完成了線程的暫停、恢復(fù)和終止工作這些方法是過(guò)期的不建議使用:因?yàn)?
suspend()
、resume()
和stop()
方法帶來(lái)的副作用,這些方法才被標(biāo)注為不建議使用的過(guò)期方法,而暫停和恢復(fù)操作可以用后面提到的等待/通知機(jī)制來(lái)替代。
安全地終止線程
通過(guò)中斷操作和
cancel()
方法均可使線程得以終止。這種通過(guò)標(biāo)識(shí)位或者中斷操作的方式能夠使線程在終止時(shí)有機(jī)會(huì)去清理資源,而不是武斷地將線程停止,因此這種終止線程的做法顯得更加安全和優(yōu)雅。
@Testpublic void test1() throws InterruptedException { Thread thread = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { logger.info("Current date:{}", System.currentTimeMillis()); } }); thread.start(); Thread.sleep(3000); thread.interrupt(); if(thread.isInterrupted()){ logger.info("Thread was interrupted.."); } Thread.sleep(3000);}
線程間通信
volatile 和 synchronized 關(guān)鍵字
關(guān)鍵字
volatile
可以用來(lái)修飾字段(成員變量),就是告知程序任何對(duì)該變量的訪問(wèn)均需要從共享內(nèi)存中獲取,而對(duì)它的改變必須同步刷新回共享內(nèi)存,它能保證所有線程對(duì)變量訪問(wèn)的可見(jiàn)性。關(guān)鍵字
synchronized
可以修飾方法或者以同步塊的形式來(lái)進(jìn)行使用,它主要確保多個(gè)線程在同一個(gè)時(shí)刻,只能有一個(gè)線程處于方法或者同步塊中,它保證了線程對(duì)變量訪問(wèn)的可見(jiàn)性和排他性。
等待/通知機(jī)制
一個(gè)線程修改一個(gè)對(duì)象的值,另一個(gè)線程感知變化。線程
A
調(diào)用對(duì)象O
的wait()
進(jìn)入等待狀態(tài),另一個(gè)線程B調(diào)用對(duì)象O
的notify()或者notifuAll()
方法,線程A
收到通知后從對(duì)象O
的wait()
方法返回,執(zhí)行后續(xù)操作。兩個(gè)線程通過(guò)對(duì)象O
來(lái)完成交互,而對(duì)象上的wait()
和notify/notifyAll()
的關(guān)系就如同開(kāi)關(guān)信號(hào)一樣,用來(lái)完成等待方和通知方之間的交互工作。等待通知相關(guān)方法如下
Consume.java
public class Consume { private static final Logger logger = LoggerFactory.getLogger(Consume.class); private final Object lockValue; public Consume(Object object) { this.lockValue = object; } /** * 生產(chǎn)者賦值 */ public void getValue() { synchronized (lockValue) { if (ObjectUtils.isEmpty(ProductConsumeValue.value)) { try { lockValue.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } } logger.info("Consume :{}", ProductConsumeValue.value); ProductConsumeValue.value = ""; lockValue.notifyAll(); } }}
Product.java
public class Product { private static final Logger logger = LoggerFactory.getLogger(Consume.class); private Object lockValue; public Product(Object lockValue) { this.lockValue = lockValue; } /** * 生產(chǎn)者賦值 */ public void setValue() { synchronized (lockValue) { if (!ObjectUtils.isEmpty(ProductConsumeValue.value)) { try { lockValue.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } } ProductConsumeValue.value = System.currentTimeMillis() + "_" + System.nanoTime(); logger.info("Product :{}", ProductConsumeValue.value); lockValue.notify(); } }}
Test.java
public static void main(String[] args) { String value = ""; Product product = new Product(value); Consume consume = new Consume(value); ProductThread productThread = new ProductThread(product); ConsumerThread consumerThread = new ConsumerThread(consume); productThread.start(); consumerThread.start();}
等待方遵循如下原則
獲取對(duì)象的鎖。
如果條件不滿足,那么調(diào)用對(duì)象的
wait
()方法,被通知后仍要檢查條件。條件滿足則執(zhí)行對(duì)應(yīng)的邏輯。
對(duì)應(yīng)的偽代碼如下:
synchronized(對(duì)象) { while(條件不滿足) { 對(duì)象.wait(); } 對(duì)應(yīng)的處理邏輯 }
通知方遵循如下原則
獲得對(duì)象的鎖。
改變條件
通知所有等待在對(duì)象上的線程。
對(duì)應(yīng)的偽代碼如下:
synchronized(對(duì)象){ 改變條件 對(duì)象.notifyAll(); }
管道輸入/輸出流
管道輸入/輸出流和普通的文件輸入/輸出流或者網(wǎng)絡(luò)輸入/輸出流不同之處在于,它 主要用于線程之間的數(shù)據(jù)傳輸,而傳輸?shù)拿浇闉閮?nèi)存。管道輸入/輸出流主要包括了如下
4
種具體實(shí)現(xiàn):PipedOutputStream
、PipedInputStream
、PipedReader
和PipedWriter
,前兩種面向字節(jié),而后兩種面向字符。
Thread.join()的使用
如果主線程處理完其他的事務(wù)后,需要用到子線程的處理結(jié)果,也就是主線程需要等待子線程執(zhí)行完成之后再結(jié)束,這個(gè)時(shí)候就要用到
join()
方法了。如果一個(gè)線程
A
執(zhí)行了thread.join()
語(yǔ)句,其含義是:當(dāng)前線程A
等待thread
線程終止之后才從thread.join()
返回。
public class ThreadJoinTest { private static final Logger logger = LoggerFactory.getLogger(ThreadJoinTest.class); /** * 如果主線程處理完其他的事務(wù)后,需要用到子線程的處理結(jié)果,也就是主線程需要等待子線程執(zhí)行完成之后再結(jié)束,這個(gè)時(shí)候就要用到 join() 方法了。 * * @param args args * @throws InterruptedException 中斷異常 */ public static void main(String[] args) throws InterruptedException { Thread currentThread = Thread.currentThread(); for (int i = 0; i < 10; i++) { JoinThreadTest joinTestTread = new JoinThreadTest(currentThread); Thread thread = new Thread(joinTestTread, "線程 " + i); thread.start(); currentThread = thread; } Thread.sleep(5000); } private static class JoinThreadTest implements Runnable { private final Thread thread; private JoinThreadTest(Thread currentThread) { thread = currentThread; } @Override public void run() { try { thread.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } logger.info("當(dāng)前線程:{}", Thread.currentThread().getName()); } }}
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-427493.html
ThreadLocal 的使用
線程變量,每個(gè)線程可以根據(jù)一個(gè)
ThreadLocal
對(duì)象查詢到綁定在這個(gè)線程上的一個(gè)值??梢酝ㄟ^(guò)set(T)
方法來(lái)設(shè)置一個(gè)值,在當(dāng)前線程下再通過(guò)get()
方法獲取到原先設(shè)置的值。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-427493.html
public class ThreadLocalTest { private static final Logger logger = Logger.getLogger(String.valueOf(ThreadLocalTest.class)); /** * 當(dāng)使用 ThreadLocal 維護(hù)變量時(shí),ThreadLocal 為每個(gè)使用該變量的線程提供獨(dú)立的變量副本, * 所以每一個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)影響其它線程所對(duì)應(yīng)的副本。 * 為了避免重復(fù)創(chuàng)建TSO(thread specific object,即與線程相關(guān)的變量) 使用 static final 修飾 */ private static final ThreadLocal<Map<String, String>> THREAD_LOCAL_MAP = new ThreadLocal<>(); @Test public void test1() { Map<String, String> map = new HashMap<>(); map.put("methodTest", "張三"); map.put("test2", "李四"); THREAD_LOCAL_MAP.set(map); getThreadLocalMap(); THREAD_LOCAL_MAP.remove(); } private void getThreadLocalMap() { Map<String, String> map = THREAD_LOCAL_MAP.get(); logger.info(String.valueOf(map)); }}
到了這里,關(guān)于Java并發(fā)編程挑戰(zhàn)與解決方案:上下文切換、死鎖、資源限制及底層實(shí)現(xiàn)原理的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!