1.有關線程、並發的基本概念


什么是線程?

  提到“線程”總免不了要和“進程”做比較,而我認為在Java並發編程中混淆的不是“線程”和“進程”的區別,而是“任務(Task)”。進程是表示資源分配的基本單位。而線程則是進程中執行運算的最小單位,即執行處理機調度的基本單位。關於“線程”和“進程”的區別耳熟能詳,說來說去就一句話:通常來講一個程序有一個進程,而一個進程可以有多個線程。

  但是“任務”是很容易忽略的一個概念。我們在實際編碼中通常會看到這么一個包叫做xxx.xxx.task,包下是XxxTask等等以Task后綴名結尾的類。而XxxTask類通常都是實現Runnable接口或者Thread類。嚴格來說,“任務”和並發編程沒多大關系,就算是單線程結構化順序編程中,我們也可以定義一個Task類,在類中執行我們想要完成的一系列操作。“任務”我認為是我們人為定義的一個概念,既抽象又具體,抽象在它指由軟件完成的一個活動,它可以是一個線程,也可以是多個線程共同達到某一目的的操作,具體在於它是我們認為指定實實在在的操作,例如:定時獲取天氣任務(定時任務),下線任務……關鍵就在於不要認為一個任務對應的就是一個線程,也許它是多個線程,甚至在這個任務中是一個線程池,這個線程池處理這個我們定義的操作。

  我產生“線程”和“任務”的疑惑就是在《Thinking in Java》這本書的“並發”章節中它將線程直接定義為一個任務,在開篇標題就取名為“定義任務”,並且提到定義任務只需實現Runnable接口.而這個任務則是通過調用start來創建一改新的線程來執行.說來說去有點繞,其實也不必糾結於在書中時而提到線程,時而提到人任務.我認為就記住:任務是我們在編程時所賦這段代碼的實際意義,而線程就關注它是否安全,是否需要安全,這就是后面要提到的線程安全問題.在像我一樣產生疑惑時,不用在意它兩者間的關系和提法.

什么是並發?

    提到了並發,那又不得不和並行作比較。並發是指在一段時間內同時做多個事情,比如在1點-2點洗碗、洗衣服等。而並行是指在同一時刻做多個事情,比如1點我左手畫圓右手畫方。兩個很重要的區別就是“一段時間”和“同一時刻”.在操作系統中就是:

  1)並發就是在單核處理中同時處理多個任務.(這里的同時指的是邏輯上的同時)

  2)並行就是在多核處理器中同時處理多個任務.(這里的同時指的就是物理上的同時)

  初學編程基本上都是單線程結構化編程,或者說是根本就接觸不到線程這個概念,反正程序照着自己實現的邏輯,程序一步一步按照我們的邏輯去實現並且得到希望輸出的結果。但隨着編程能力的提高,以及應用場景的復雜多變,我們不得不要面臨多線程並發編程。而初學多線程並發編程時,常常出現一些預料之外的結果,這就是涉及到“線程安全”問題。

什么線程安全?

    這是在多線程並發編程中需要引起足夠重視的問題,如果你的線程不足夠“安全”,程序就可能出現難以預料,以及難以復現的結果。《Java並發編程實戰》提到對線程安全不好做一個定義,我的簡單理解就是:線程安全就是指程序按照你的代碼邏輯執行,並始終輸出預定的結果。書中的給的定義:當多個線程訪問某個類時,這個類始終都能表現出正確的行為,那么就稱這個類是線程安全的。具體有關線程安全的問題,例如原子性、可見性等等不在這里做詳細闡述,適當的時候會進行詳細介紹,簡單說一點,想要這個線程安全,得在訪問的時候給它上個鎖,不讓其他線程訪問,當然這種說法不嚴謹,不過可以暫時這么理解。

  以上是從基本概念理論出發來大致了解需要知道的一些概念,下面就針對JDK中有關線程的API來對多線程並發編程做一個了解。

java.lang.Object
    -public void notify()//喚醒這個對象監視器正在等待獲取鎖的一個線程
    -public void notifyAll()//喚醒這個對象監視器上正在等待獲取鎖的所有線程
    -public void wait()//導致當前線程等待另一個線程調用notify()或notifyAll()
    -public void wait(long timeout)// 導致當前線程等待另一個線程調用notify()或notifyAll(),或者達到timeout時間
    -public void wait(long timeout, int nanos)//與上個方法相同,只是將時間控制到了納秒nanos

  我們先用一個經典的例子——生產者消費者問題來說明上面的API是如何使用的。生產者消費者問題指的的是,生產者生產產品到倉庫里,消費者從倉庫中拿,倉庫滿時生產者不能繼續生產,倉庫為空時消費者不能繼續消費。轉化成程序語言也就是生產者是一個線程1,消費者是線程2,倉庫是一個隊列,線程1往隊尾中新增,線程2從隊首中移除,隊列滿時線程1不能再新增,隊列空時線程2不能再移除。

package com.producerconsumer;

import java.util.Queue;



/**

 * 生產者

 * Created by yulinfeng on 2017/5/11.

 */

public class Producer implements Runnable{

    private final Queue<String> queue;

    private final int maxSize;

    public Producer(Queue<String> queue, int maxSize) {

        this.queue = queue;

        this.maxSize = maxSize;

    }
    public void run() {
        produce();
    }

    /**

     * 生產

     */

    private void produce() {

        try {
            while (true) {
                synchronized (queue) {
                    if (queue.size() == maxSize) {
                        System.out.println("生產者:倉庫滿了,等待消費者消費");
                        queue.wait();
                    }
                    System.out.println("生產者:" + queue.add("product"));
                    queue.notifyAll();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 

package com.producerconsumer;

import java.util.Queue;

/**

 * 消費者

 * Created by yulinfeng on 2017/5/11.

 */

public class Consumer implements Runnable {

    private final Queue<String> queue;
    public Consumer(Queue<String> queue) {
        this.queue = queue;
    }

    public void run() {
        consume();
    }

 

    /**

     * 消費

     */

    private void consume() {
        synchronized (queue) {
            try {
                while (true) {
                    if (queue.isEmpty()) {
                        System.out.println("消費者:倉庫空了,等待生產者生產");
                        queue.wait();
                    }
                    System.out.println("消費者:" + queue.remove());
                    queue.notifyAll();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

 

package com.producerconsumer;

import java.util.LinkedList;
import java.util.Queue;

/**

 * Created by yulinfeng on 2017/5/11.

 */

public class Main {

    public static void main(String[] args) {

        Queue<String> queue = new LinkedList<String>();
        int maxSize = 100;
        Thread producer = new Thread(new Producer(queue, maxSize));
        Thread consumer = new Thread(new Consumer(queue));
        producer.start();
        consumer.start();

    }

}

 

  這個生產者消費者問題的實現,我采用線程不安全的LinkedList,使用內置鎖synchronized來保證線程安全,在這里我們不討論synchronized,主要談notify()、notifyAll()和wait()。

  在這里例子中,作為生產者,當隊列滿時調用了隊列的wait()方法,表示等待,並且此時釋放了鎖。作為消費者此時獲取到鎖並且移除隊首元素時調用了notifyAll()方法,此時生產者由wait等待狀態轉換為喚醒狀態,但注意!此時僅僅是線程被喚醒了,有了爭奪CPU資源的資格,並不代表下一步就一定是生產者生產,還有可能消費者繼續爭奪了CPU資源。一定記住是被喚醒了,有資格爭奪CPU資源。notifyAll()表示的是喚醒所有等待的線程,所有等待的線程被喚醒過后,都有了爭奪CPU資源的權利,至於是誰會獲得這個鎖,那不一定。而如果是使用notify(),那就代表喚醒所有等待線程中的一個,只是一個被喚醒具有了爭奪CPU的權力,其他沒被喚醒的線程繼續等待。如果等待線程就只有一個那么notify()和notifyAll()就沒區別,不止一個那區別就大了,一個是只喚醒其中一個,一個是喚醒所有。喚醒不是代表這個線程就一定獲得CPU資源一定獲得鎖,而是有了爭奪的權利。

java.lang.Thread
    -public void join()
    -public void sleep()
    -public static void yield()
    -……

  針對Thread線程類,我們只說常見的幾個不容易理解的方法,其余方法不在這里做詳細闡述。

  關於sleep()方法,可能很容易拿它和Object的wait方法作比較。兩個方法很重要的一點就是sleep不會釋放鎖,而wait會釋放鎖。在上面的生產者消費者的生產或消費過程中添加一行Thread.sleep(5000),你將會發現執行到此處時,這個跟程序都會暫停執行5秒,不會有任何其他線程執行,因為它不會釋放鎖。

  關於join()方法,JDK7的解釋是等待線程結束(Waits for this thread to die)似乎還是不好理解,我們在main函數中啟動兩個線程,在啟動完這兩個線程后main函數再執行其他操作,但如果不加以限制,有可能main函數率先執行完需要的操作,但如果在main函數中加入join方法,則表示阻塞等待這兩個線程執行結束后再執行main函數后的操作,例如:

package com.join;

/**

 * Created by 余林豐 on 2017/5/11/0012.

 */
public class Main {

    public static void main(String[] args) throws Exception{
        Thread t1 = new Thread(new Task(0));
        Thread t2 = new Thread(new Task(0));
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.print("main結束");
    }
}

  上面個例子如果沒有join方法,那么“main”結束這條輸出語句可能就會先於t1、t2,加上在啟動線程的調用方使用了線程的join方法,則調用方則會阻塞線程執行結束過后再執行剩余的方法。

  關於Thread.yield()方法,本來這個線程處於執行狀態,其他線程也想爭奪這個資源,突然,這個線程不想執行了想和大家一起來重新奪取CPU資源。所以Thread.yield也稱讓步。從下一章開始就正式開始了解java.util.concurrent。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM