????????????????????????JUC
????????????????????????????????原創(chuàng)者:文思磷雇,感謝尚硅谷,資料來(lái)源于尚硅谷
目錄:
1望艺、volatile關(guān)鍵字與內(nèi)存可見(jiàn)性
2苛秕、原子變量與CAS算法
3、同步容器類
4找默、閉鎖操作
5艇劫、Callable接口(常用)
6、Lock同步鎖
7惩激、讀寫鎖
8店煞、線程八鎖
9岖赋、線程池
10既琴、線程調(diào)度
11、ForkJoinPool分支/合并框架 工作竊取
一绍赛、JUC
1骡技、volatile關(guān)鍵字與內(nèi)存可見(jiàn)性
??? Java5.0提供了java.util.concurrent(簡(jiǎn)稱JUC )包鸣个,在此包中增加了在并發(fā)編程中很常用的實(shí)用工具類,用于定義類似于線程的自定義子系統(tǒng)哮兰,包括線程池毛萌、異步IO 和輕量級(jí)任務(wù)框架苟弛。提供可調(diào)的喝滞、靈活的線程池。還提供了設(shè)計(jì)用于多線程上下文中的Collection實(shí)現(xiàn)等膏秫。
內(nèi)存可見(jiàn)性(Memory Visibility)是指當(dāng)某個(gè)線程正在使用對(duì)象狀態(tài)而另一個(gè)線程在同時(shí)修改該狀態(tài)右遭,需要確保當(dāng)一個(gè)線程修改了對(duì)象狀態(tài)后,其他線程能夠看到發(fā)生的狀態(tài)變化缤削。
???可見(jiàn)性錯(cuò)誤是指當(dāng)讀操作與寫操作在不同的線程中執(zhí)行時(shí)窘哈,我們無(wú)法確保執(zhí)行讀操作的線程能適時(shí)地看到其他線程寫入的值,有時(shí)甚至是根本不可能的事情亭敢。
如下代碼示例:
public classTestVolatile {
??? public static void main(String[] args){
?????? Testt = new Test();
?????? new Thread(t).start();
?????? while(true){
?????????? if(t.getFlag()){
????????????? System.out.println("-----enter main while if----");
????????????? break;
?????????? }
?????? }
??? }
}
class Test implementsRunnable{
??? private boolean flag = false;
??? @Override
??? public void run() {
?????? try{
?????????? Thread.sleep(1000);
?????? }catch(Exception e){ }
?????? flag = true;
?????? System.out.println("------this is test child thread-----");
??? }
??? public boolean getFlag(){
?????? return this.flag;
??? }
}
運(yùn)行結(jié)果:
----------this is test child thread-----------
主線程main的------enter main while if-------一直沒(méi)進(jìn)入執(zhí)行滚婉。這就是發(fā)生了內(nèi)存可見(jiàn)性錯(cuò)誤,上述程序執(zhí)行流程:
由于布爾變量flag偏底層帅刀,所以jvm執(zhí)行布爾運(yùn)算時(shí)非橙酶梗快,所以在test子線程內(nèi)復(fù)制修改flag之后扣溺,還沒(méi)有來(lái)得及寫入main主線程骇窍,main主線程就進(jìn)入到while中,導(dǎo)致如上效果锥余。
如果用同步synchronized鎖則往往是噩夢(mèng)的開(kāi)始:
while(true){
?????????? synchronized (t) {
????????????? if(t.getFlag()){
????????????????? System.out.println("-----enter main while if-----");
????????????????? break;
????????????? }
?????????? }?????????
?????? }
這樣會(huì)導(dǎo)致極低的效率腹纳。同步鎖具有互斥性,如果多個(gè)縣城訪問(wèn)這塊,如果一個(gè)線程已持有這個(gè)鎖嘲恍,另一個(gè)線程發(fā)現(xiàn)后就阻塞在這里等待鎖的釋放足画,然后就等待另一個(gè)線程調(diào)用完釋放鎖,然后cpu再調(diào)度蛔钙。所以不要輕易甚至不要使用同步synchronized鎖锌云。這就需要volatile關(guān)鍵字:private volatile boolean flag = false。
/*
?*一吁脱、volatile關(guān)鍵字當(dāng)多個(gè)線程進(jìn)行操作共享數(shù)據(jù)時(shí)桑涎,可以保證內(nèi)存中的數(shù)據(jù)可見(jiàn)。
?*? ? ? 相較于 synchronized 是一種較為輕量級(jí)的同步策略兼贡。
?*注意:
?* 1. volatile不具備“互斥性”
?* 2. volatile不能保證變量的“原子性”
?*/
public classTestVolatile {
??? public static void main(String[] args){
?????? Testt = new Test();
?????? new Thread(t).start();?????
?????? while(true){
?????????? if(t.getFlag()){
????????????? System.out.println("------enter main while if------");
????????????? break;
?????????? }?????????
?????? }
??? }
}
class Test implementsRunnable{
??? private volatile boolean flag = false;
??? @Override
??? public void run() {
?????? try{
?????????? Thread.sleep(1000);
?????? }catch(Exception e){????
?????? }
?????? flag = true;
?????? System.out.println("-------this is test child thread-----");
??? }
??? public boolean getFlag(){
?????? return this.flag;
??? }
}
運(yùn)行結(jié)果:
-----------enter main while if---------
----------this is test child thread-----------
程序執(zhí)行流程:
子線程不會(huì)同步變量到自己的線程緩存中攻冷,直接修改主內(nèi)存中的變量。
Java提供了一種稍弱的同步機(jī)制遍希,即volatile 變量等曼,用來(lái)確保將變量的更新操作通知到其他線程≡渌猓可以將volatile 看做一個(gè)輕量級(jí)的鎖禁谦,但是又與鎖有些不同:
?對(duì)于多線程,不是一種互斥關(guān)系
?不能保證變量狀態(tài)的“原子性”操作
2废封、原子變量與CAS算法
volatile只能解決變量同步問(wèn)題州泊,但解決不了并發(fā)中的原子性問(wèn)題(原子性問(wèn)題不再演示)。i++ 的操作實(shí)際上分為三個(gè)步驟“讀-改-寫”
int i = 10; i = i++; 相當(dāng)于:int temp = i; i = i + 1; i = temp;可看出僅僅依靠volatile解決不了原子性問(wèn)題漂洋。
Jdk提供了原子變量來(lái)解決此問(wèn)題:
Java.util.concurrent.atomic類的小工具包遥皂,支持在單個(gè)變量上解除鎖的線程安全編程。事實(shí)上刽漂,此包中的類可將volatile 值演训、字段和數(shù)組元素的概念擴(kuò)展到那些也提供原子條件更新操作的類.
類AtomicBoolean、AtomicInteger贝咙、AtomicLong 和AtomicReference 的實(shí)例各自提供對(duì)相應(yīng)類型單個(gè)變量的訪問(wèn)和更新样悟。每個(gè)類也為該類型提供適當(dāng)?shù)膶?shí)用工具方法。AtomicIntegerArray庭猩、AtomicLongArray 和AtomicReferenceArray 類進(jìn)一步擴(kuò)展了原子操作窟她,對(duì)這些類型的數(shù)組提供了支持。這些類在為其數(shù)組元素提供volatile 訪問(wèn)語(yǔ)義方面也引人注目眯娱,這對(duì)于普通數(shù)組來(lái)說(shuō)是不受支持的礁苗。
核心方法:booleancompareAndSet(expectedValue, updateValue)
java.util.concurrent.atomic 包下提供了一些原子操作的常用類:
?AtomicBoolean 、AtomicInteger 徙缴、AtomicLong 试伙、AtomicReference
?AtomicIntegerArray 嘁信、AtomicLongArray
?AtomicMarkableReference
?AtomicReferenceArray
?AtomicStampedReference
CAS (Compare-And-Swap) 是一種硬件對(duì)并發(fā)的支持,針對(duì)多處理器操作而設(shè)計(jì)的處理器中的一種特殊指令疏叨,用于管理對(duì)共享數(shù)據(jù)的并發(fā)訪問(wèn)
CAS 是一種無(wú)鎖的非阻塞算法的實(shí)現(xiàn)
CAS 包含了3 個(gè)操作數(shù):
1潘靖、需要讀寫的內(nèi)存值V
2、進(jìn)行比較的值A(chǔ)
3蚤蔓、擬寫入的新值B
當(dāng)且僅當(dāng)V 的值等于A 時(shí)卦溢,CAS 通過(guò)原子方式用新值B 來(lái)更新V 的值,否則不會(huì)執(zhí)行任何操作
示例:
/*
?*一秀又、i++ 的原子性問(wèn)題:i++ 的操作實(shí)際上分為三個(gè)步驟“讀-改-寫”
?*??? ??inti = 10;
?*??? ??i = i++; //10
?*??? ??inttemp= i;
?*??? ??i = i + 1;
?*??? ??i =temp;
?*二单寂、原子變量:在 java.util.concurrent.atomic 包下提供了一些原子變量。
?*??? 1.volatile保證內(nèi)存可見(jiàn)性
?*??? 2.CAS(Compare-And-Swap) 算法保證數(shù)據(jù)變量的原子性
?*??????? CAS算法是硬件對(duì)于并發(fā)操作的支持
?*??????? CAS包含了三個(gè)操作數(shù):
?*??????? ①內(nèi)存值? V
?*??????? ②預(yù)估值? A
?*??????? ③更新值? B
?*??????? 當(dāng)且僅當(dāng) V == A 時(shí)吐辙, V = B; 否則宣决,不會(huì)執(zhí)行任何操作。
?*/
public classTestAtomicDemo {
??? public static void main(String[] args) {
?????? AtomicDemoa = new AtomicDemo();
?????? for(int i=0;i<10;i++){//啟動(dòng)10個(gè)子線程
?????????? new Thread(a).start();
?????? }
??? }
}
class AtomicDemo implements Runnable{
??? private AtomicInteger number = new AtomicInteger(0);
??? public void run() {
?????? try{
?????????? Thread.sleep(2000);
?????? }catch(Exception e){????
?????? }
?????? System.out.println("number="+this.handlerNumber());
??? }
??? public int handlerNumber(){//子線程里加1
?????? return number.getAndIncrement();
??? }
}
以上使用原子性變量解決了原子性問(wèn)題昏苏。CAS算法是基于硬件底層算法實(shí)現(xiàn)的尊沸,也不是使用鎖的互斥原理,不會(huì)釋放cpu的控制權(quán)贤惯,所以效率比鎖高洼专。
3、同步容器類
HashMap與HashTable的區(qū)別之一是HashMap線程不安全孵构,HashTable線程安全屁商,所以HashTable的效率也低。
Java 5在java.util.concurrent 包中提供了多種并發(fā)容器類來(lái)改進(jìn)同步容器的性能浦译。
ConcurrentHashMap同步容器類是Java 5 增加的一個(gè)線程安全的哈希表棒假。對(duì)與多線程的操作溯职,介于HashMap 與Hashtable 之間精盅。內(nèi)部采用“鎖分段”機(jī)制替代Hashtable 的獨(dú)占鎖,進(jìn)而提高性能谜酒。
此包還提供了設(shè)計(jì)用于多線程上下文中的Collection 實(shí)現(xiàn):ConcurrentHashMap叹俏、ConcurrentSkipListMap、ConcurrentSkipListSet僻族、CopyOnWriteArrayList 和CopyOnWriteArraySet粘驰。當(dāng)期望許多線程訪問(wèn)一個(gè)給定collection 時(shí),ConcurrentHashMap 通常優(yōu)于同步的HashMap述么,ConcurrentSkipListMap 通常優(yōu)于同步的TreeMap蝌数。當(dāng)期望的讀數(shù)和遍歷遠(yuǎn)遠(yuǎn)大于列表的更新數(shù)時(shí),CopyOnWriteArrayList 優(yōu)于同步的ArrayList度秘。
JDK1.8對(duì)ConcurrentSkipListMap又取消了分段鎖顶伞,又采用回了CAS。
示例:
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
/*
?*CopyOnWriteArrayList/CopyOnWriteArraySet :“寫入并復(fù)制”
?*注意:添加操作多時(shí),效率低唆貌,因?yàn)槊看翁砑訒r(shí)都會(huì)進(jìn)行復(fù)制滑潘,開(kāi)銷非常的大。并發(fā)迭代操作多時(shí)可以選擇锨咙。
?*/
public class TestCopyOnWriteArrayList {
?????? publicstatic void main(String[] args) {
????????????? HelloThreadht = new HelloThread();
????????????? for(int i = 0; i < 10; i++) {
???????????????????? newThread(ht).start();
????????????? }
?????? }
}
class HelloThread implements Runnable{
?????? private staticCopyOnWriteArrayList list = new CopyOnWriteArrayList<>();
?????? static{
????????????? list.add("AA");
????????????? list.add("BB");
????????????? list.add("CC");
?????? }
?????? @Override
?????? publicvoid run() {
????????????? Iteratorit = list.iterator();
????????????? while(it.hasNext()){
???????????????????? System.out.println(it.next());
???????????????????? list.add("AA");
????????????? }
?????? }
}
4语卤、閉鎖操作
CountDownLatch 一個(gè)同步輔助類,在完成一組正在其他線程中執(zhí)行的操作之前酪刀,它允許一個(gè)或多個(gè)線程一直等待粹舵。
閉鎖可以延遲線程的進(jìn)度直到其到達(dá)終止?fàn)顟B(tài),閉鎖可以用來(lái)確保某些活動(dòng)直到其他活動(dòng)都完成才繼續(xù)執(zhí)行:
1骂倘、確保某個(gè)計(jì)算在其需要的所有資源都被初始化之后才繼續(xù)執(zhí)行;
2齐婴、確保某個(gè)服務(wù)在其依賴的所有其他服務(wù)都已經(jīng)啟動(dòng)之后才啟動(dòng);
3、等待直到某個(gè)操作所有參與者都準(zhǔn)備就緒再繼續(xù)執(zhí)行
示例:
/*
?* CountDownLatch:閉鎖稠茂,在完成某些運(yùn)算是柠偶,只有其他所有線程的運(yùn)算全部完成,當(dāng)前運(yùn)算才繼續(xù)執(zhí)行
?*/
public classTestCountDownLatch {
??? public static void main(String[] args) {
//new CountDownLatch(10)里面的參數(shù)是倒計(jì)時(shí)初始化個(gè)數(shù)睬关,因?yàn)橄旅鏁?huì)創(chuàng)建10個(gè)線程诱担,每啟動(dòng)一個(gè)線程就減1,當(dāng)為0是就說(shuō)明所有線程創(chuàng)建完畢
?????? final CountDownLatch latch = new CountDownLatch(10);
?????? LatchDemold = new LatchDemo(latch);
?????? long startTime= System.currentTimeMillis();
?????? for(int i=0;i<10;i++){
?????????? new Thread(ld).start();
?????? }
?????? try {
?????????? latch.await();
?????? }catch(InterruptedException e) {
?????? }
?????? long endTime= System.currentTimeMillis();
?????? System.out.println("----------"+(endTime-startTime));
??? }
}
class LatchDemo implements Runnable{
??? private CountDownLatch latch;
??? publicLatchDemo(CountDownLatch latch){
?????? this.latch = latch;
??? }
??? @Override
??? public void run() {
?????? for(int i=0;i<50000;i++){
?????????? if(1%2==0){
????????????? System.out.println(i);
?????????? }
?????? }
?????? latch.countDown();
??? }
}
一定要在await()之前必須調(diào)用countDown()進(jìn)行遞減操作电爹。
5蔫仙、Callable接口(常用)
以前都認(rèn)為創(chuàng)建線程的方式是兩種:繼承Thread類和實(shí)現(xiàn)Runnable接口。現(xiàn)在可歸結(jié)為四種丐箩,還有:實(shí)現(xiàn)Callable接口與
Java 5.在java.util.concurrent 提供了一個(gè)新的創(chuàng)建執(zhí)行線程的方式:Callable 接口摇邦。
Callable 接口類似于Runnable,兩者都是為那些其實(shí)例可能被另一個(gè)線程執(zhí)行的類設(shè)計(jì)的屎勘。但是Runnable 不會(huì)返回結(jié)果施籍,并且無(wú)法拋出經(jīng)過(guò)檢查的異常。
Callable 需要依賴FutureTask 概漱。
根據(jù)以上分析FutureTask 也可以用作閉鎖丑慎,實(shí)現(xiàn)閉鎖的效果。
示例:
/*
?*一瓤摧、創(chuàng)建執(zhí)行線程的方式三:實(shí)現(xiàn)Callable 接口竿裂。 相較于實(shí)現(xiàn) Runnable 接口的方式,方法可以有返回值照弥,并且可以拋出異常腻异。
?*二、執(zhí)行Callable 方式这揣,需要 FutureTask 實(shí)現(xiàn)類的支持悔常,用于接收運(yùn)算結(jié)果敢会。? FutureTask是? Future接口的實(shí)現(xiàn)類
?*/
public classTestCallable {
??? public static void main(String[] args) {
?????? int t = 0;
?????? ThreadDemotd = new ThreadDemo(t);
?????? FutureTaskresult= newFutureTask(td);
?????? long starttime= System.currentTimeMillis();
?????? new Thread(result).start();
?????? try {
?????????? IntegerresultInt= result.get();
?????????? long endtime= System.currentTimeMillis();
?????????? System.out.println("---time---"+(endtime-starttime));
?????????? System.out.println("---resultInt---"+resultInt);
?????? }catch(InterruptedException e) {
?????? }catch(ExecutionException e){
? ? ? ?}
??? }
}
class ThreadDemo implements Callable{
??? private Integer count;
??? public ThreadDemo(Integer count){
?????? this.count = count;
??? }
??? @Override
??? public Integer call() throws Exception {
?????? for(int i=0;i<1000000000;i++){
?????????? count++;
?????? }
?????? return count;
??? }
}
運(yùn)行結(jié)果:
---time---3369
---resultInt---1000000000
6、Lock同步鎖
在jdk1.5以前这嚣,解決線程安全只能用同步代碼塊和同步方法以及volatile 鸥昏。
Jdk1.5后可以用同步鎖Lock,這是一個(gè)顯示鎖姐帚,需要通過(guò)lock()方法上鎖吏垮,通過(guò)unlock()方法釋放鎖。但并不是一種替代內(nèi)置鎖的方法罐旗,而是當(dāng)內(nèi)置鎖不適用時(shí)膳汪,作為一種可選擇的高級(jí)功能。
ReentrantLock 實(shí)現(xiàn)了Lock 接口九秀,并提供了與synchronized 相同的互斥性和內(nèi)存可見(jiàn)性遗嗽。但相較于synchronized 提供了更高的處理鎖的靈活性。
不加鎖的同步問(wèn)題:
加鎖示例:
/*
?*一鼓蜒、用于解決多線程安全問(wèn)題的方式:
?* synchronized:隱式鎖
?* 1.同步代碼塊. 2. 同步方法
?*jdk1.5后:
?* 3.同步鎖 Lock
?*注意:是一個(gè)顯示鎖痹换,需要通過(guò) lock() 方法上鎖,必須通過(guò) unlock() 方法進(jìn)行釋放鎖
?*/
public classTestLock {
??? public static void main(String[] args){
?????? LockDemold = new LockDemo();
?????? new Thread(ld,"1號(hào)售票點(diǎn),").start();
?????? new Thread(ld,"2號(hào)售票點(diǎn),").start();
?????? new Thread(ld,"3號(hào)售票點(diǎn),").start();
??? }
}
class LockDemo implements Runnable{
??? private volatile int count=10;
??? private Lock lock = new ReentrantLock();
??? @Override
??? public void run() {
?????? while(count>0){
?????????? lock.lock();
?????????? try {
????????????? Thread.sleep(100);
?????????? }catch(InterruptedException e) {
?????????? }finally{
????????????? lock.unlock();
?????????? }
?????????? count--;
?????????? System.out.println(Thread.currentThread().getName()+"剩余票數(shù):"+count);
?????? }??
??? }
}
7都弹、讀寫鎖
寫寫,讀寫 需要"互斥"娇豫,讀讀 不需要"互斥",這樣即避免了線程安全問(wèn)題又提高了效率畅厢。
一個(gè)讀寫鎖維護(hù)了一個(gè)讀鎖和一個(gè)寫鎖冯痢,其中讀鎖可以多個(gè)線程使用,寫鎖只能一個(gè)線程獨(dú)占框杜。
/**
?*寫寫,讀寫 需要"互斥"
?*讀讀 不需要"互斥"
?*/
public classTestReadWriteLock {
??? public static void main(String[] args){
?????? ReadWriteLockDemod = new ReadWriteLockDemo();
?????? new Thread(new Runnable(){
?????????? @Override
?????????? public void run() {
????????????? d.write(1000000000);
?????????? }
?????? },"write").start();
?????? for(int i=0;i<10;i++){
?????????? new Thread(new Runnable(){
????????????? @Override
????????????? public void run() {
????????????????? d.read();
????????????? }
?????????? },"read").start();
?????? }
??? }
}
class ReadWriteLockDemo{
??? private int number = 0;
??? ReadWriteLocklock= newReentrantReadWriteLock();
??? public void read(){
?????? lock.readLock().lock();;
?????? try{
??????? System.out.println(Thread.currentThread().getName()+":number="+this.number);
?????? }finally{
?????????? lock.readLock().unlock();
?????? }
??? }
??? public void write(int number){
?????? lock.writeLock().lock();
?????? try{
?????????? System.out.println(Thread.currentThread().getName());
?????????? this.number = number;
?????? }finally{
?????????? lock.writeLock().unlock();
?????? }?????
??? }
}
運(yùn)行結(jié)果:
write
read:number=1000000000
read:number=1000000000
read:number=1000000000
read:number=1000000000
read:number=1000000000
read:number=1000000000
read:number=1000000000
read:number=1000000000
read:number=1000000000
read:number=1000000000
8浦楣、線程八鎖
a:兩個(gè)普通同步方法,兩個(gè)線程咪辱,標(biāo)準(zhǔn)打印
public classTestThread8demo {
??? public static void main(String[] args) {
?????? Numbern = new Number();
?????? new Thread(new Runnable(){
?????????? @Override
?????????? public void run() {
????????????? n.getOne();
?????????? }
?????? }).start();
?????? new Thread(new Runnable(){
?????????? @Override
?????????? public void run() {
????????????? n.getTwo();
?????????? }
?????? }).start();
??? }
}
class Number{
??? public synchronized void getOne(){
?????? System.out.println("One");
??? }
??? public synchronized void getTwo(){
?????? System.out.println("Two");
??? }
}
運(yùn)行結(jié)果:立刻打印出
One
Two
b:兩個(gè)普通同步方法振劳,其中一個(gè)線程休眠3秒后,兩個(gè)線程再標(biāo)準(zhǔn)打印
public synchronizedvoidgetOne(){
?????? try {
?????????? Thread.sleep(3000);
?????? }catch(InterruptedException e) {
?????? }
?????? System.out.println("One");
??? }
??? public synchronized void getTwo(){
?????? System.out.println("Two");
??? }
運(yùn)行結(jié)果:3秒后打印出
One
Two
推論:
一個(gè)對(duì)象里面如果有多個(gè)synchronized方法梧乘,某一個(gè)時(shí)刻內(nèi)澎迎,只要一個(gè)線程去調(diào)用其中的一個(gè)synchronized方法了庐杨,其它的線程都只能等待选调,換句話說(shuō),某一個(gè)時(shí)刻內(nèi)灵份,只能有唯一一個(gè)線程去訪問(wèn)這些synchronized方法仁堪。
鎖的是當(dāng)前對(duì)象this,被鎖定后填渠,其它的線程都不能進(jìn)入到當(dāng)前對(duì)象的其它的synchronized方法弦聂。
c:新增一個(gè)普通方法(這樣一個(gè)同步鎖并休眠3秒鸟辅,一個(gè)同步鎖,一個(gè)普通方法)
public classTestThread8demo {
??? public static void main(String[] args) {
?????? Numbern = new Number();
?????? new Thread(new Runnable(){
?????????? @Override
?????????? public void run() {
????????????? n.getOne();
?????????? }
?????? }).start();
?????? new Thread(new Runnable(){
?????????? @Override
?????????? public void run() {
?????????? ??? n.getTwo();
?????????? }
?????? }).start();
?????? new Thread(new Runnable(){
?????????? @Override
?????????? public void run() {
????????????? n.getThree();
?????????? }
?????? }).start();
??? }
}
class Number{
??? public synchronized void getOne(){
?????? try {
?????????? Thread.sleep(3000);
?????? }catch(InterruptedException e) {
?????? }
?????? System.out.println("One");
??? }
??? public synchronized void getTwo(){
?????? System.out.println("Two");
??? }
??? public void getThree(){
?????? System.out.println("Three");
??? }
}
運(yùn)行結(jié)果:
Three
3秒后……
One
Two
推論:普通方法與同步鎖無(wú)關(guān)莺葫。
d:兩個(gè)普通的同步方法匪凉,同時(shí)兩個(gè)對(duì)象訪問(wèn),打印
public classTestThread8demo {
??? public static void main(String[] args) {
?????? Numbern = new Number();
?????? Numbern2 = new Number();
?????? new Thread(new Runnable(){
?????????? @Override
?????????? public void run() {
????????????? n.getOne();
?????????? }
?????? }).start();
?????? new Thread(new Runnable(){
?????????? @Override
?????????? public void run() {
????????????? n2.getTwo();
?????????? }
?????? }).start();
??? }
}
class Number{
??? public synchronized void getOne(){
?????? try {
?????????? Thread.sleep(3000);
?????? }catch(InterruptedException e) {
?????? }
?????? System.out.println("One");
??? }
??? public synchronized void getTwo(){
?????? System.out.println("Two");
??? }??
}
運(yùn)行結(jié)果:
Two
3秒后……
One
推論:
換成兩個(gè)對(duì)象后捺檬,就不是同一把鎖了再层,彼此不受影響。更進(jìn)一步印證了synchronized最小作用域的范圍是對(duì)象堡纬。
也就是說(shuō)如果一個(gè)實(shí)例對(duì)象的非靜態(tài)同步方法獲取鎖后聂受,該實(shí)例對(duì)象的其他非靜態(tài)同步方法必須等待獲取鎖的方法釋放鎖后才能獲取鎖,可是別的實(shí)例對(duì)象的非靜態(tài)同步方法因?yàn)楦搶?shí)例對(duì)象的非靜態(tài)同步方法用的是不同的鎖烤镐,所以毋須等待該實(shí)例對(duì)象已獲取鎖的非靜態(tài)同步方法釋放鎖就可以獲取他們自己的鎖蛋济。
e:修改getOne為靜態(tài)同步,然后一個(gè)對(duì)象訪問(wèn)打印
public classTestThread8demo {
??? public static void main(String[] args) {
?????? Numbern = newNumber();
?????? new Thread(new Runnable(){
?????????? @Override
?????????? public void run() {
????????????? n.getOne();
?????????? }
?????? }).start();
?????? new Thread(new Runnable(){
?????????? @Override
?????????? public void run() {
????????????? n.getTwo();
?????????? }
?????? }).start();
??? }
}
class Number{
??? public static synchronized void getOne(){
?????? try {
?????????? Thread.sleep(3000);
?????? }catch(InterruptedException e) {
?????? }
?????? System.out.println("One");
??? }
??? public synchronized void getTwo(){
?????? System.out.println("Two");
??? }
}
運(yùn)行結(jié)果:
Two
3秒后……
One
推論:
所有的靜態(tài)同步方法用的也是同一把鎖:類對(duì)象本身炮叶,和非靜態(tài)對(duì)象鎖是兩個(gè)不同的對(duì)象碗旅,所以靜態(tài)同步方法與非靜態(tài)同步方法之間是不會(huì)有競(jìng)態(tài)條件的。所以two先執(zhí)行了镜悉,one休眠后繼續(xù)執(zhí)行扛芽。但是請(qǐng)看g
f:修改getOne和getTwo都為靜態(tài)同步,然后一個(gè)對(duì)象訪問(wèn)打印
public classTestThread8demo {
??? public static void main(String[] args) {
?????? Numbern = new Number();
?????? Numbern2= newNumber();
?????? new Thread(new Runnable(){
?????????? @Override
?????????? public void run() {
????????????? n.getOne();
?????????? }
?????? }).start();
?????? new Thread(new Runnable(){
?????????? @Override
?????????? public void run() {
????????????? n.getTwo();
?????????? }
?????? }).start();
??? }
}
class Number{
??? public static synchronized void getOne(){
?????? try {
?????? ??? Thread.sleep(3000);
?????? }catch(InterruptedException e) {
?????? }
?????? System.out.println("One");
??? }
??? public static synchronized void getTwo(){
?????? System.out.println("Two");
??? }
}
運(yùn)行結(jié)果:
3秒后…
One
Two
推論:
進(jìn)一步且只印證了所有的靜態(tài)同步方法用的也是同一把鎖:類對(duì)象本身积瞒,但是請(qǐng)看g
g:修改getOne和getTwo都為靜態(tài)同步川尖,然后兩個(gè)對(duì)象訪問(wèn)打印
public classTestThread8demo {
??? public static void main(String[] args) {
?????? Numbern = new Number();
?????? Numbern2 = newNumber();
?????? new Thread(new Runnable(){
?????????? @Override
?????????? public void run() {
????????????? n.getOne();
?????????? }
?????? }).start();
?????? new Thread(new Runnable(){
?????????? @Override
?????????? public void run() {
????????????? n2.getTwo();
?????????? }
?????? }).start();
??? }
}
class Number{
??? public static synchronized void getOne(){
?????? try {
?????????? Thread.sleep(3000);
?????? }catch(InterruptedException e) {
?????? }
?????? System.out.println("One");
??? }
??? public static synchronized void getTwo(){
?????? System.out.println("Two");
??? }
}
運(yùn)行結(jié)果:
3秒后……
One
Two
推論:
所有的靜態(tài)同步方法用的也是同一把鎖:類對(duì)象本身,和非靜態(tài)對(duì)象鎖是兩個(gè)不同的對(duì)象茫孔,所以靜態(tài)同步方法與非靜態(tài)同步方法之間是不會(huì)有競(jìng)態(tài)條件的叮喳。但是一旦一個(gè)靜態(tài)同步方法獲取鎖后,其他的靜態(tài)同步方法都必須等待該方法釋放鎖后才能獲取鎖缰贝。
h:只修改getOne為靜態(tài)同步馍悟,然后兩個(gè)對(duì)象訪問(wèn)打印
public classTestThread8demo {
??? public static void main(String[] args) {
?????? Numbern = new Number();
?????? Numbern2 = new Number();
?????? new Thread(new Runnable(){
?????????? @Override
?????????? public void run() {
????????????? n.getOne();
?????????? }
?????? }).start();
?????? new Thread(new Runnable(){
?????????? @Override
?????????? public void run() {
????????????? n2.getTwo();
?????????? }
?????? }).start();
??? }
}
class Number{
??? public static synchronized void getOne(){
?????? try {
?????????? Thread.sleep(3000);
?????? }catch(InterruptedException e) {
?????? }
?????? System.out.println("One");
??? }
??? public synchronized void getTwo(){
?????? System.out.println("Two");
??? }
}
運(yùn)行結(jié)果:
Two
3秒后……
One
推論:進(jìn)一步印證了靜態(tài)同步方法的鎖是類對(duì)象本身,非靜態(tài)方法的鎖是對(duì)象本身剩晴,兩類鎖不是同一把鎖锣咒。
線程八鎖關(guān)鍵點(diǎn)總結(jié):
I:非靜態(tài)方法的鎖默認(rèn)為? this,? 靜態(tài)方法的鎖為 對(duì)應(yīng)的 Class 實(shí)例。對(duì)象鎖與類鎖不互相干擾赞弥,不會(huì)有競(jìng)態(tài)條件毅整。
Ii:某一個(gè)時(shí)刻內(nèi),只能有一個(gè)線程持有鎖绽左,無(wú)論幾個(gè)方法悼嫉。
9、線程池
第四種獲取線程的方法:線程池拼窥。
提供一個(gè)線程隊(duì)列戏蔑,隊(duì)列中保存著所有等待狀態(tài)的線程蹋凝,避免了創(chuàng)建與銷毀的開(kāi)銷,提高了響應(yīng)的速度总棵。
線程池的體系結(jié)構(gòu):
?* java.util.concurrent.Executor :負(fù)責(zé)線程的使用與調(diào)度的根接口
?*??????? |--**ExecutorService子接口: 線程池的主要接口
?*?????????????? |--ThreadPoolExecutor線程池的實(shí)現(xiàn)類
?*?????????????? |--ScheduledExecutorService子接口:負(fù)責(zé)線程的調(diào)度
?*??????????????????????????? |--ScheduledThreadPoolExecutor:繼承 ThreadPoolExecutor鳍寂, 實(shí)現(xiàn) ScheduledExecutorService
可以看到,雖然ExecutorService下有許多子接口和實(shí)現(xiàn)類情龄,但是這里建議使用Executors來(lái)獲取線程伐割。
工具類: Executors
?* ExecutorServicenewFixedThreadPool() :創(chuàng)建固定大小的線程池
?* ExecutorServicenewCachedThreadPool() :緩存線程池,線程池的數(shù)量不固定刃唤,可以根據(jù)需求自動(dòng)的更改數(shù)量隔心。可自動(dòng)進(jìn)行線程回收尚胞。
?* ExecutorServicenewSingleThreadExecutor() :創(chuàng)建單個(gè)線程池硬霍。線程池中只有一個(gè)線程
?*ScheduledExecutorService newScheduledThreadPool() :創(chuàng)建固定大小的線程,可以延遲或定時(shí)的執(zhí)行任務(wù)笼裳。
示例1:
public classTestThreadPool {
??? public static void main(String[] args) {
?????? //1創(chuàng)建有5個(gè)線程的線程池
?????? ExecutorServiceexecutorService=? Executors.newFixedThreadPool(5);
?????? //執(zhí)行10次線程唯卖,為線程分配任務(wù)
?????? for (int i = 0; i < 10; i++) {
?????????? executorService.submit(new Runnable(){
????????????? @Override
????????????? public void run() {
????????????????? int sum = 0;
????????????????? for (int i = 0; i < 100; i++) {
???????????????????? sum ++;
????????????????? }??
??? ??? System.out.println(Thread.currentThread().getName()+"="+sum);
????????????? }
?????????? });
?????? }
?????? //關(guān)閉線程,executorService.shutdownNow()是立即關(guān)閉
?????? executorService.shutdown();
??? }
}
運(yùn)行結(jié)果:
pool-1-thread-1:1
pool-1-thread-1:3
pool-1-thread-1:4
pool-1-thread-2:0
pool-1-thread-3:7
pool-1-thread-3:9
pool-1-thread-5:94
pool-1-thread-2:98
pool-1-thread-2:99
可以看到始終thread1-5,都用的線程池里的5個(gè)線程躬柬。
示例2:使用帶返回值的Callable接口
public classTestThreadPool {
??? public static void main(String[] args) throwsInterruptedException,ExecutionException {
?????? //1創(chuàng)建有5個(gè)線程的線程池
?????? ExecutorServiceexecutorService=? Executors.newFixedThreadPool(5);
?????? //為線程分配10個(gè)任務(wù)
?????? for(int i=0;i<10;i++){
?????????? Futureresult= executorService.submit(new Callable(){
????????????? @Override
????????????? public Integer call() throws Exception {
????????????????? int count = 0;
????????????????? for(int i=0;i<100;i++){
???????????????????? count++;
????????????????? }
????????????????? return count;
????????????? }??
?????????? });
??? ?????? System.out.println(result.get());????????
?????? }
?????? //關(guān)閉線程拜轨,executorService.shutdownNow()是立即關(guān)閉
?????? executorService.shutdown();
??? }
}
10、線程調(diào)度
即ScheduledExecutorService的使用示例:
public classTestScheduledThreadPool {
??? public static void main(String[] args) throws InterruptedException,
ExecutionException {
?????? ScheduledExecutorServicepool= Executors.newScheduledThreadPool(5);
?????? //3個(gè)參數(shù),第1個(gè)是線程執(zhí)行體,第2個(gè)延時(shí)多久,第3個(gè)延時(shí)單位
?????? for(int i=0;i<3;i++){
?????????? Futureresult= pool.schedule(newCallable(){
????????????? @Override
????????????? public Integer call() throws Exception {
?????? ?????????? int t = newRandom().nextInt();
?????????????????? System.out.println(Thread.currentThread().getName()+"="+t);
????????????????? return t;
????????????? }?????
?????????? },3, TimeUnit.SECONDS);//延時(shí)3秒執(zhí)行
?????? }
?????? pool.shutdown();
??? }
}
運(yùn)算結(jié)果:
3秒后……
pool-1-thread-2=-1843839421
pool-1-thread-3=-1333144512
pool-1-thread-1=1075550443
程序中Future result = pool.schedule(new?Callable(){ 括號(hào)外加輸出語(yǔ)句:
public staticvoidmain(String[] args)throwsInterruptedException, ExecutionException {
?????? ScheduledExecutorServicepool= Executors.newScheduledThreadPool(5);
?????? //3個(gè)參數(shù),第1個(gè)是線程執(zhí)行體,第2個(gè)延時(shí)多久,第3個(gè)延時(shí)單位
?????? for(int i=0;i<3;i++){
?????????? Futureresult= pool.schedule(newCallable(){
????????????? @Override
????????????? public Integer call() throws Exception {
????????????????? int t = newRandom().nextInt();??????? ??? System.out.println(Thread.currentThread().getName()+"="+t);
????????????????? return t;
????????????? }?????
?????????? },3, TimeUnit.SECONDS);//延時(shí)3秒執(zhí)行
?????? ??? System.out.println(Thread.currentThread().getName()+"="+result.get());
?????? }
?????? pool.shutdown();
??? }
運(yùn)行結(jié)果:
3秒后……
pool-1-thread-1=-506702518
main=-506702518
pool-1-thread-1=173222792
main=173222792
pool-1-thread-2=-1115855720
main=-1115855720
發(fā)現(xiàn)pool.schedule體外就是main主線程了允青。
二橄碾、ForkJoinPool分支/合并框架 工作竊取
Fork/Join 框架:就是在必要的情況下,將一個(gè)大任務(wù)颠锉,進(jìn)行拆分(fork)成若干個(gè)小任務(wù)(拆到不可再拆時(shí))法牲,再將一個(gè)個(gè)的小任務(wù)運(yùn)算的結(jié)果進(jìn)行join 匯總。
采用“工作竊取”模式(work-stealing):
當(dāng)執(zhí)行新的任務(wù)時(shí)它可以將其拆分分成更小的任務(wù)執(zhí)行琼掠,并將小任務(wù)加到線程隊(duì)列中拒垃,然后再?gòu)囊粋€(gè)隨機(jī)線程的隊(duì)列中偷一個(gè)并把它放在自己的隊(duì)列中。
相對(duì)于一般的線程池實(shí)現(xiàn)瓷蛙,fork/join框架的優(yōu)勢(shì)體現(xiàn)在對(duì)其中包含的任務(wù)的處理方式上.在一般的線程池中悼瓮,如果一個(gè)線程正在執(zhí)行的任務(wù)由于某些原因無(wú)法繼續(xù)運(yùn)行,那么該線程會(huì)處于等待狀態(tài)艰猬。而在fork/join框架實(shí)現(xiàn)中横堡,如果某個(gè)子問(wèn)題由于等待另外一個(gè)子問(wèn)題的完成而無(wú)法繼續(xù)運(yùn)行。那么處理該子問(wèn)題的線程會(huì)主動(dòng)尋找其他尚未運(yùn)行的子問(wèn)題來(lái)執(zhí)行.這種方式減少了線程的等待時(shí)間姥宝,提高了性能翅萤。
jdk7和jdk8的使用不一樣,現(xiàn)在分別舉例:
首先20億循環(huán)計(jì)算查看耗時(shí):
1731毫秒腊满。
jdk7反面示例1(拆分臨界值太小,導(dǎo)致拆分太多):
public classTestForkJoinPool {
??? public static void main(String[] args) {
?????? long starttime= System.currentTimeMillis();
?????? ForkJoinPoolpool= newForkJoinPool();
?????? ForkJoinTasktask= newForkJoinSumCalculate(0l,2000000000l);
?????? long sum = pool.invoke(task);
?????? long endtime= System.currentTimeMillis();
?????? System.out.println(sum);
?????? System.out.println("---20億循環(huán)計(jì)算耗時(shí):"+(endtime-starttime));
??? }
}
class ForkJoinSumCalculate extends RecursiveTask{
??? private static final long serialVersionUID= -259195479995561737L;
??? private long start;
??? private long end;
??? private static final long THURSHOLD= 10000L;//任務(wù)拆分臨界值(1萬(wàn))
??? public ForkJoinSumCalculate(long start,long end){
?????? this.start = start;
?????? this.end = end;
??? }
??? @Override
??? protected Long compute() {
?????? long length = end -start;
?????? if(length<THURSHOLD){
?????????? long sum = 0L;
?????????? for(long i=start;i<=end;i++){
????????????? sum+=1;
?????????? }
?????????? return sum;
?????? }else{
?????????? long middle = (start-end)/2;
?????????? ForkJoinSumCalculateleft= newForkJoinSumCalculate(start,middle);
?????????? left.fork();//進(jìn)行拆分并壓入線程隊(duì)列
?????????? ForkJoinSumCalculateright= newForkJoinSumCalculate(middle,end);
?????????? right.fork();//進(jìn)行拆分并壓入線程隊(duì)列
?????????? return left.join() + right.join();
?????? }
??? }
}
運(yùn)行結(jié)果:
Exception in thread "main"java.lang.StackOverflowError
?????? atsun.reflect.GeneratedConstructorAccessor1.newInstance(Unknown Source)
?????? at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
?????? atjava.lang.reflect.Constructor.newInstance(Constructor.java:423)
?????? atjava.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:598)
?????? at java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:677)
棧溢出了套么。大家知道內(nèi)存分堆(堆內(nèi)存)和棧(棧內(nèi)存),棧主要存變量及定義碳蛋。我猜測(cè)拆分的變量都放在棧中胚泌,臨界值小,從而拆的多肃弟,所以棧中的變量多玷室,就益處了,那調(diào)大臨界值試試看笤受,從1萬(wàn)調(diào)到100萬(wàn)試試看:
private static final long THURSHOLD = 100000000L;//任務(wù)拆分臨界值100萬(wàn)穷缤,還報(bào)棧內(nèi)存溢出 ,那就改成1000萬(wàn):private static final long THURSHOLD = 1000000000L;//任務(wù)拆分臨界值1000萬(wàn)
不報(bào)錯(cuò)了箩兽,成功拆分津肛,但貌似性能比不拆分慢很多,所以這種任務(wù)拆分不一定能提升性能汗贫,要根據(jù)機(jī)器硬件來(lái)適當(dāng)選擇身坐。
使用jdk1.8示例:
public classTestForkJoinPool {
??? public static void main(String[] args) {
?????? long starttime= System.currentTimeMillis();
?????? Long sum= LongStream.rangeClosed(0L, 2000000000l)
????????????? ?.parallel()
????????????? ?.reduce(0L, Long::sum);
?????? long endtime= System.currentTimeMillis();
?????? System.out.println(sum);
?????? System.out.println("---20億循環(huán)計(jì)算耗時(shí):"+(endtime-starttime));
??? }
}
運(yùn)行結(jié)果:
2000000001000000000
---20億循環(huán)計(jì)算耗時(shí):1620
用了1620毫秒,快了一點(diǎn)落包。