java線程(四)
java5線程並發庫
線程並發庫是JDK 1.5版本級以上才有的針對線程並發編程提供的一些常用工具類,這些類被封裝在java.concurrent包下。
該包下又有兩個子包,分別是atomic和locks兩個包。
java.util.concurrent.atomic包
atomic包提供了一些線程相關的工具類,來實現共享數據在多個線程中保持安全訪問而不用使用 synchronized關鍵字進行同步。下面是該報下的一些類。
這裏就拿AtomicInteger類來舉例,其他類的操作基本上和該類差不多。在JDK的API中說該類可以以原子的方式操作int值,通俗的說就是該類提供了一下對整數類型變量的操作使用該類可以確保在多個線程中訪問同一個整數資源時及時不適用鎖機制來保持同步也依然能夠確保該變數據的安全。下面是給類提供的一些方法。
1 package cn.wz.traditional.wf; 2 3 import java.util.concurrent.atomic.AtomicInteger; 4 5 /** 6 * Created by WangZhe on 2017/5/8. 7 */ 8 public class AtomicTest { 9 static AtomicInteger data =new AtomicInteger(10); 10 public static void main(String[] args) { 11 new Thread(new以addAndGet方法為例的一個demoRunnable() { 12 public void run() { 13 int resoult = data.addAndGet(10); 14 System.out.println(resoult); 15 } 16 }).start(); 17 new Thread(new Runnable() { 18 public void run() { 19 intresoult = data.get(); 20 System.out.println(resoult); 21 } 22 }).start(); 23 } 24 }
那麽Atomic包中的類為什麽能夠實現數據操作的原始性呢?這個我就不得而知了,因為前sun公司並沒有把它體現在源碼中。我只知道在addAndGet方法中調用get()方法獲取變量的原有值,而get方法中返回的value字段被volatile關鍵字標示,該關鍵子線程每次獲取該變量的值時都會去主內存區獲取該變量最新的值,但是這裏值得註意的是volatile關鍵字並不能像synchronized關鍵字一樣確保變量的原子性,至於AtomicInteger是如何確保其原子性的我也不得而知,希望有知道的大神能夠分享下,再此先謝過了!下面附上addAndGet方法的源碼.
該方法實現被前sun公司隱藏。
Unsafe類的compareAndSwapInt方法(真正的為變量賦值操作)被隱藏
好了其源碼就看到這裏了其它的類和方法也都差不多,如果有興趣你可以自己看看源碼(然而並沒有什麽卵用,呵呵),其實我們也不用糾結於其實怎麽實現的我們只要知道其可以確保原子性,在需要的是後能夠使用就可以了,其和 synchronized同步代碼塊而言效率和性能是較高的,但其只能適用於一些簡單的賦值運算操作,因為我們可以看到其並沒有提供乘除以及其它的復雜操作。
java.util.concurrent.locks包
該包提供了一些關於線程鎖相關的一些接口和類,我們之前使用synchronized關鍵字只能設置對象、類、變量等固定的鎖,而是用該包則可以時鎖變得更加靈活和廣泛,該包有三大接口
Condition:Condition
將 Object
監視器方法(wait
、notify
和 notifyAll
)分解成截然不同的對象,以便通過將這些對象與任意 Lock
實現組合使用,為每個對象提供多個等待 set(wait-set)。
Lock:Lock
實現提供了比使用 synchronized
方法和語句可獲得的更廣泛的鎖定操作。
ReadWriteLock:ReadWriteLock 維護了一對相關的鎖
,一個用於只讀操作,另一個用於寫入操作。
Lock 接口方法摘要:
1 package cn.wz.traditional.wf; 2 3 import java.util.concurrent.locks.Lock; 4 import java.util.concurrent.locks.ReentrantLock; 5 6 /** 7 * Created by WangZhe on 2017/5/8. 8 */ 9 public class LockDemo { 10 static int data=5; 11 public static void main(String[] args) { 12 final Lock lock=new ReentrantLock(true); 13 new Thread(new Runnable() { 14 public void run() { 15 lock.lock(); 16 try { 17 System.out.println("線程1獲取鎖\t時間:"+System.currentTimeMillis()); 18 data+=5; 19 System.out.println("線程1休眠1秒鐘\t正在等待該所的線程:"+((ReentrantLock)lock).getQueueLength()); 20 System.out.println(data); 21 Thread.currentThread().sleep(1000); 22 }catch (Exception e){ 23 lock.unlock(); 24 }finally { 25 lock.unlock(); 26 }; 27 } 28 }).start(); 29 new Thread(new Runnable() { 30 public void run() { 31 lock.lock(); 32 try { 33 System.out.println("線程2獲取鎖\t時間:"+System.currentTimeMillis()); 34 System.out.println(data); 35 }catch (Exception e){ 36 lock.unlock(); 37 }finally { 38 lock.unlock(); 39 }; 40 } 41 }).start(); 42 } 43 }LockDemo
從上面的demo中可以看出Lock接口不再指定同步鎖定的對象,而是一起本身為鎖對象別synchronized更加靈活且沒有synchronized關鍵字必須在同一個代碼塊兒的限制。上面demo中使用的ReentrantLock類是Lock接口的實現類Lock接口下面還提供了控制讀寫鎖的實現類:ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.WriteLock兩個類,而這兩個類都是ReentrantReaWriteLock類的內部類,如圖所示:
而ReentrantReadWriteLock類是ReadWriteLock接口的實現類。這裏就在順便說一下這個接口。該接口只有兩個方法定義如下所示:
一個適用於獲取ReadLock的鎖另一個是用於獲取WriteLock的鎖這兩個鎖一般成對出現用來分離數據的讀和寫進行加鎖,比如說一個線程對A資源進行寫入操作時另一個線程讀取A資源時就需要等待上一個線程釋放A資源的寫入鎖才能獲取A資源的讀取鎖進行數據讀取,反之亦然。
1 package cn.wz.traditional.wf; 2 3 import java.util.concurrent.locks.Lock; 4 import java.util.concurrent.locks.ReentrantReadWriteLock; 5 6 /** 7 * Created by WangZhe on 2017/5/8. 8 */ 9 public class ReadLockDemo { 10 static int data=10; 11 12 public static void main(String[] args) { 13 final ReentrantReadWriteLock RWlock = new ReentrantReadWriteLock();//獲取讀寫鎖實例 14 final ReentrantReadWriteLock.ReadLock Rlock=RWlock.readLock();//獲取讀取所實例 15 final ReentrantReadWriteLock.WriteLock Wlock=RWlock.writeLock();//獲取寫入鎖實例 16 new Thread(new Runnable() { 17 public void run() {//線程一 18 Wlock.lock(); 19 try{ 20 data=5; 21 System.out.println(data); 22 System.out.println("線程一寫入數據完畢保持寫入鎖休眠10秒鐘"); 23 System.out.println("當前時間:"+System.currentTimeMillis()); 24 Thread.currentThread().sleep(10000); 25 }catch (Exception e){ 26 Wlock.unlock(); 27 }finally { 28 Wlock.unlock(); 29 } 30 } 31 }).start(); 32 new Thread(new Runnable() { 33 public void run() {//線程二 34 Rlock.lock(); 35 try{ 36 System.out.println("線程二獲取讀取鎖,開始進行數據讀取"); 37 System.out.println("當前時間:"+System.currentTimeMillis()); 38 System.out.println(data); 39 }catch (Exception e){ 40 Rlock.unlock(); 41 }finally { 42 Rlock.unlock(); 43 } 44 } 45 }).start(); 46 } 47 }讀寫鎖演示demo
Condition接口
Condition接口的出現代替了Object對象的三個改變線程的方法(wait、notify、notifyall)如果說Lock相當於synchronized關鍵字那Condition就相當於Object監視器的方法。下面是該接口的方法
其中await方法相當於Object的wait方法,Signal方法相當於Object的notify方法,signalAll方法相當於Object對像的notifyAll方法。這裏不再詳細說明直接通過demo來演示效果。場景如下:
AB兩個線程同時操作資源i A線程對i進行加操作,B線程對i進行減操作,當i大於5時A線程等待讓B線程進行減操作,反之B線程等待讓A線程進行加操作。
1 package cn.wz.traditional.wf; 2 3 import java.util.concurrent.locks.Condition; 4 import java.util.concurrent.locks.Lock; 5 import java.util.concurrent.locks.ReentrantLock; 6 7 /** 8 * Created by WangZhe on 2017/5/9. 9 */ 10 public class ConditionDemo { 11 public static void main(String[] args) { 12 final Lock lock=new ReentrantLock(); 13 final Condition conditionInc=lock.newCondition();; 14 final Condition conditionDec=lock.newCondition(); 15 final DemoData data=new DemoData(); 16 new Thread(new Runnable() { 17 public void run() { 18 lock.lock(); 19 try { 20 for (int j = 0; j < 50; j++) { 21 while (data.getI() > 5) { 22 conditionInc.await(); 23 } 24 data.inc(); 25 conditionDec.signal(); 26 } 27 } catch (Exception e) { 28 lock.unlock(); 29 } finally { 30 lock.unlock(); 31 } 32 } 33 }).start(); 34 new Thread(new Runnable() { 35 public void run() { 36 lock.lock(); 37 try{ 38 for(int j=0;j<50;j++) { 39 while (data.getI() <= 5) { 40 conditionDec.await(); 41 } 42 data.dec(); 43 conditionInc.signal(); 44 } 45 }catch (Exception e){ 46 lock.unlock(); 47 }finally { 48 lock.unlock(); 49 } 50 } 51 }).start(); 52 } 53 static class DemoData{ 54 private int i; 55 56 public DemoData(int i) { 57 this.i = i; 58 } 59 60 public DemoData() { 61 } 62 public void inc(){ 63 i++; 64 System.out.println("線程A進行加操作後i的值:"+i); 65 } 66 public void dec(){ 67 68 i--; 69 System.out.println("線程B進行減操作後i的值:"+i); 70 } 71 public int getI() { 72 return i; 73 } 74 75 public void setI(int i) { 76 this.i = i; 77 } 78 } 79 }Demo
java線程池的應用
我們知道線程一旦死亡就無法復活(進行重新啟動線程。)但是在黑馬的面試題中關於java並發的問題中有這樣一道面試題:“如何重新啟動一個已經死亡的線程?”。剛開始碰到這個問題的人可能會很驚訝,為什麽會有這樣的面試題呢?我們先來看下面一個demo算不算是重新啟動了一個死亡的線程。
1 package cn.wz.traditional.wf; 2 3 import com.sun.org.apache.xpath.internal.SourceTree; 4 5 import java.util.concurrent.Executor; 6 import java.util.concurrent.ExecutorService; 7 import java.util.concurrent.Executors; 8 import java.util.concurrent.ThreadPoolExecutor; 9 10 /** 11 * Created by WangZhe on 2017/5/9. 12 */ 13 public class ExecutorSignlePool { 14 public static void main(String[] args) { 15 ExecutorService executorService = Executors.newSingleThreadExecutor(); 16 executorService.execute(new MyRunnable()); 17 18 } 19 static class MyRunnable implements Runnable{ 20 public void run() { 21 for (int i=0;i<10;i++){ 22 if(i==5) 23 Thread.currentThread().stop(); 24 System.out.println("hello"); 25 } 26 } 27 } 28 }Demo
從上面例子中可以看到我沒在循環到第五次的時候停止當前線程使其死亡,但是我們從結果中可以看到程序並沒有結束,那就說明還有線程正在運行,那我們是否可以這樣假設當停止當前線程時線程池(executorService)一直試圖重新啟動線程但重啟之後立馬又被我們的代碼關掉所以一直執行不下去,但也不會結束。那具體是不是這樣我也不知道(哈哈),但我知道的是單例線程池(也就是我們上邊demo中的executorService)中只能存在一個線程,當期線程死亡後會立馬創建一個新的線程, 從一定程度上也可以說是重新啟動了一個線程,而且即使線程執行完畢也不會結束除非手動停止線程池
1 package cn.wz.traditional.wf; 2 3 import com.sun.org.apache.xpath.internal.SourceTree; 4 5 import java.util.concurrent.Executor; 6 import java.util.concurrent.ExecutorService; 7 import java.util.concurrent.Executors; 8 import java.util.concurrent.ThreadPoolExecutor; 9 10 /** 11 * Created by WangZhe on 2017/5/9. 12 */ 13 public class ExecutorSignlePool { 14 public static void main(String[] args) { 15 ExecutorService executorService = Executors.newSingleThreadExecutor(); 16 MyRunnable myRunnable = new MyRunnable(); 17 executorService.execute(myRunnable); 18 executorService.execute(myRunnable); 19 20 } 21 static class MyRunnable implements Runnable{ 22 public void run() { 23 System.out.println("開始執行"); 24 System.out.println("hello"); 25 } 26 } 27 }demo
除了單線程池意外我們開可以創建固定大小的線程池(newFixedThreadPool方法創建)和緩存線程池(newCachedThreadPool方法創建)這兩個方法都返回ExecutorService類型對象。
固定大小的線程池:該池中的線程個數是固定的當線程任務(Runnable對象)個數大於設置的線程個數時超出的任務等待之前任務執行後再執行。
緩存線程池:該池初始沒有線程,當有任務進來時創建線程並在執行結束後緩存一段時間。在改時間之內有新的任務進來則不再創建新線程直接使用原有線程。
java線程(四)