看過之後覺得這一章顯得雜亂。但是正如書中說的,學習併發編程就像進入一個全新的領域,本章的目的就是要讓讀者對併發的基礎知識打下堅實的基礎。
1 併發的多面性
用併發解決的問題大體上可以分爲“速度”和“設計可管理型”兩種。、
阻塞:如果程序中某個任務因爲該程序控制範圍之外的某些條件(通常是I/O)而導致不能繼續執行,那麼我們就說這個任務或線程阻塞了。
2 基本的線程機制
併發編程使我們可以將程序劃分爲多個分離的、獨立運行的任務。通過使用多線程機制,這些獨立任務(也被稱爲子任務)中的每一個都將執行線程來驅動。一個線程就是在進程中的一個單一的順序控制流,因此,單個進城可以擁有多個 併發執行的任務。
- Runnable接口。要想定義任務,只需實現Runnable接口並編寫run()方法。
public class LiftOff implements Runnable{
protected int countDown = 10;
private static int taskCount = 0;
private final int id = taskCount++;
public LiftOff(){}
public LiftOff(int countDown){
this.countDown = countDown;
}
public String status() {
return "#"+id+"("+ (countDown > 0 ? countDown : "LiftOff!") + "), ";
}
@Override
public void run() {
while(countDown-- > 0){
System.out.println(status());
Thread.yield();
}
}
}
- Thread類。將Runnable對象轉變爲工作任務的傳統方式是把它提交給一個Thread構造器。調用Thread對象的start()方法爲該線程執行必須的初始化操作,然後調用Runnable的run()方法,以便在這個新線程中啓動該任務。
public class BasicThreads {
public static void main(String[] args) {
Thread t = new Thread(new LiftOff());
t.start();
System.out.println("Waiting for LiftOff");
}
}
- 執行器(Executor)。java.util.concurrent包中的Executor將爲你管理Thread對象,從而簡化了併發編程。Executor允許你管理異步任務的執行,而無須顯式地管理線程的生命週期。ExecutorService(具有服務生命週期的Executor,例如關閉)知道如何構建恰當的上下文來執行Runnable對象。ExecutorService對象是使用靜態的Executer方法創建的,這個方法可以確定其Executor類型:
👽CachedThreadPool。在程序中執行過程中通常會創建與所需數量相同的線程,然後在它回收舊線程時停止創建新線程,因此它時合理的Executor的首選。
👽 FixedThreadPool。使用了有限的線程集來執行所提交的任務。
👽SingleThreadExecutor。如果向SingleThreadExecutor提交了多個任務,那麼這些任務將排隊,每個任務都會在下一個任務開始之前運行結束,所有的任務將使用相同的線程。
public class CachedThreadPool {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
for(int i = 0;i < 5;i++)
exec.execute(new LiftOff());
exec.shutdown();
}
}
- Callable接口。Runnable是執行工作的獨立任務,但是它不返回任何值。如果需要返回值可以實現Callable接口,Callable是一種具有類型參數的泛型,它的類型參數表示的是從方法call()中返回的值,並且必須使用ExecutorServiec.submit()方法調用它。submit()方法會產生Future對象。可以用isDone()方法來查詢Future是否已經完成。當任務完成時,它具有一個結果,可以調用get()方法來獲取該結果。
class TaskWithResult implements Callable<String>{
private int id;
public TaskWithResult(int id){
this.id = id;
}
public String call(){
return "result of TaskWithResult " + id;
}
}
public class CallableDemo {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
ArrayList<Future<String>> results = new ArrayList<Future<String>>();
for(int i=0;i<10;i++)
results.add(exec.submit(new TaskWithResult(i)));
for(Future<String> fs : results){
try{
System.out.println(fs.get());
}catch(InterruptedException e){
System.out.println(e);
return;
}catch(ExecutionException e){
System.out.println(e);
}finally{
exec.shutdown();
}
}
}
}
- sleep()休眠。影響任務行爲的一種簡單方法是調用sleep(),這將使任務中止執行給定的時間。對sleep()的調用可以拋出InterruptedException異常,並且可以在run()中被捕獲。(TimeUnit.MILLISECONDS.sleep(500)😉
public class SleepingTask extends LiftOff{
public void run(){
try{
while(countDown-- > 0){
System.out.println(status());
TimeUnit.MILLISECONDS.sleep(100);
}
}catch(InterruptedException e){
System.out.println("Interrupted");
}
}
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
for(int i=0;i<5;i++)
exec.execute(new SleepingTask());
exec.shutdown();
}
}
- 優先級。線程的優先級將該線程的重要性傳遞給了調度器。優先級較低的線程僅僅是執行的頻率較低。
🏃♀️可以用getPriority()來讀取現有的線程的優先級
🏃♀️並且在任何時刻都可以通過setPriority()來修改它。
public class SimplePriorities implements Runnable{
private int countDown = 5;
private volatile double d;
private int priority;
public SimplePriorities(int priority){
this.priority = priority;
}
public String toString(){
//可以在一個任務的內部,通過調用Thread.currentThread()來獲得對驅該任務的Thread對象的引用。
return Thread.currentThread() + ": "+countDown;
}
@Override
public void run() {
Thread.currentThread().setPriority(priority); //設置優先級
while(true){
for(int i=1;i<100000;i++){
d += (Math.PI + Math.E)/(double)i;
//if(i % 1000 == 0)
//Thread.yield();
}
System.out.println(this);
if(--countDown == 0)
return;
}
}
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
for(int i=0;i<5;i++){
exec.execute(new SimplePriorities(Thread.MIN_PRIORITY)); //優先級的級別
}
exec.execute(new SimplePriorities(Thread.MAX_PRIORITY));
exec.shutdown();
}
}
- yield()讓步。
- 後臺(daemon)線程。所謂後臺線程是指,在程序運行的時候在後臺提供一種通用服務的線程,並且這種線程不屬於程序中不可或缺的部分。因此,當所有的非後臺線程結束時,程序也就終止了,同時會殺死進程中的所有後臺線程。反過來說,只要有任何非後臺線程還在運行,程序就不會終止。比如,執行main()的就是一個非後臺線程。
🏃♀️必須在線程啓動之前調用setDaemon()方法,才能把它設置爲後臺線程。
🏃♀️可以通過調用isDaemon()方法來確定線程是否是一個後臺線程。 - 加入一個線程。一個線程可以在其他現呈上調用join()方法,其效果是等待一段時間直到第二個線程結束才繼續執行。對join()的調用可以被中斷,做法是在調用線程上調用interrrupt()方法,這時需要用到try-catch子句。
class Sleeper extends Thread{
private int duration;
public Sleeper(String name, int sleepTime){
super(name);
duration = sleepTime;
start();
}
public void run(){
try{
sleep(duration);
}catch(InterruptedException e){
print(getName()+" was interrupted. "+"isInterrrupted(): "+isInterrupted());
return;
}
print(getName()+" has awakened");
}
}
class Joiner extends Thread {
private Sleeper sleeper;
public Joiner(String name, Sleeper sleeper){
super(name);
this.sleeper = sleeper;
start();
}
public void run(){
try{
sleeper.join();//加入
}catch(InterruptedException e){
print(getName()+" was interrupted. "+"isInterrrupted(): "+isInterrupted());
}
print(getName() + " join completed");
}
}
public class Joining {
public static void main(String[] args) {
Sleeper
sleepy = new Sleeper("Sleepy",1500),
grumpy = new Sleeper("Grumpy",1500);
Joiner
dopey = new Joiner("Dopey",sleepy),
doc = new Joiner("doc",grumpy);
grumpy.interrupt(); //中斷
}
}
3. 共享受限資源
- 解決資源共享競爭
👽synchronized
👽Biran的同步規則:如果你正在寫一個變量,它可能接下來將被另一個程序讀取,或者正在讀取一個上一次已經被另一個線程寫過的變量,那麼你必須使用同步,並且,讀寫線程都必須用相同的監視器鎖同步。
👽使用顯示的Lock對象。java.util.concurrent類庫還包含有定義在java.util.concurrent.locks中的顯式的互斥機制。Lock對象必須被顯式地創建、鎖定和釋放。相對於內建的synchronized鎖來說,還賦予了你更細粒度的控制力。
public class MutexEvenGenerator extends IntGenerator{
private int currentEvenValue = 0;
private Lock lock = new ReentrantLock(); //創建
public int next(){
lock.lock(); //鎖定
try{
++currentEvenValue;
Thread.yield();
++currentEvenValue;
return currentEvenValue;
}finally{
lock.unlock();//釋放
}
}
public static void main(String[] args) {
EvenChecker.test(new MutexEvenGenerator());
}
}
- 原子性與易變性。volatile關鍵字。
- 原子類。AtomicIntefer、AtomicLong、AtomicReference等特殊的原子性變量類,它們提供了下面形式的原子性條件更新操作:
boolean compareAndSet(expectedValue, updateValue);
- 臨界區。有時,只是希望防止多個線程同時訪問方法內部的部分代碼而不是防止訪問整個方法。通過這種方式分離出來的代碼被稱爲臨界區,它也使用synchronized關鍵字建立。這裏,synchronized被用來制定某個對象,此對象的鎖被用來對花括號內的代碼進行同步控制。這也被稱爲同步控制塊;在進入此段代碼前,必須得到syncObjec對象的鎖。如果其他線程已經得到這個鎖,那麼就得等到鎖被釋放以後,才能進入臨界區。
synchronized(syncObject) {
//This code can be accessed by only one task at a time
}