程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Thinking in Java -- 並發(一)

Thinking in Java -- 並發(一)

編輯:JAVA綜合教程

Thinking in Java -- 並發(一)


並發的多面性


基本的並發

定義任務

線程可以驅動任務,我們通過實現 Runnable 接口來提供,需要實現 Runnable 接口的 run() 方法。

package concurrency;

/**
 * Created by wwh on 16-3-24.
 */
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!") + "), ";
    }

    public void run() {
        while (countDown-- > 0) {
            System.out.println(status());
            /* Thread.yield() 是對線程調度器的一種建議,可以將CPU從一個線程轉移給另一個線程 */
            Thread.yield();
        }
    }
}
package concurrency;

/**
 * Created by wwh on 16-3-24.
 */
public class MainThread {
    public static void main(String[] args) {
        LiftOff lauch = new LiftOff();
        lauch.run();
    }
}

Runnable

我們也可以通過繼承 Thread 類覆蓋 run() 方法來實現線程類,但繼承Thread類有一個缺點就是單繼承,而實現Runnable接口則彌補了它的缺點,可以實現多繼承。而且實現 Runnable 接口適合多線程共享資源,繼承 Thread 類適合各個線程完成自己的任務,因為繼承 Thread 類相當於每個線程有一份各自的資源,而實現 Runnable 還可以讓多個線程共享一份代碼。


Thread 類

將 Runnable 對象轉變為工作任務的傳統方式是將它提交給一個 Thread 構造器。

public class BasicThreads {
    public static void main(String[] args) {
        for (int i = 0; i < 5; ++i) {
            new Thread(new LiftOff()).start();
        }
        System.out.println("Waiting for LiftOff");
    }
}

Thread 構造器只需要一個 Runnable 對象。調用 Thread 對象的 start() 方法為該線程執行必須的初始化操作,然後內部調用 Runnable 的 run() 方法。


使用 Executor

Java SE5 並發包中引入執行器可以為我們管理線程 Thread 對象。

package concurrency;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by wwh on 16-3-24.
 */
public class CachedThreadPool {
    public static void main(String []args) {
        /* ExecutorServive 是具有聲明周期的 Executor */
        ExecutorService exec = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; ++i) {
            exec.execute(new LiftOff());
        }
        /* shutdown 被用來防止新任務被提交給 Executor */
        exec.shutdown();
    }
}

ThreadPool 種類很多。如下圖:
線程池

包括
CacheThreadPool:為每個任務都創建一個線程池
FixedThreadPool:一次性預先分配好固定大小的線程
SingleThreadExecutor:線程數唯一,提交多個任務會排隊等候
ScheduledThreadPool:創建一個定長線程池,支持定時及周期性任務執行。<喎?http://www.Bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vc3Ryb25nPjwvcD4NCjxociAvPg0KPGgyIGlkPQ=="從任務中產生返回值">從任務中產生返回值

如果我們希望任務完成時能夠返回一個值,那麼可以實現 Callable 接口而不是 Runnable 接口,實現 Callable 接口要求覆蓋 Call() 方法。

package concurrency;

import java.util.ArrayList;
import java.util.concurrent.*;

/**
 * Created by wwh on 16-3-24.
 */

class TaskWithResult implements Callable {
    private int id;
    public TaskWithResult(int id) {
        this.id = id;
    }
    public String call() throws Exception {
        return "reslut of TaskWithResult " + id;
    }
}

public class CallableDemo {
    public static void main(String []args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        /* 定義 Future 對象,在將來獲取 */
        ArrayList> results =
                new ArrayList>();
        for (int i = 0; i < 10; ++i) {
            /* submit() 方法會產生 Future 對象,可以通過 Future 對象的 isDone() 方法來判斷查詢 Future 是否已經完成,當完成時,可以使用 get() 方法獲取結果 */
            results.add(exec.submit(new TaskWithResult(i)));
        }
        for (Future fs : results) {
            try {
                System.out.println(fs.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            } finally {
                exec.shutdown();
            }
        }
    }
}

Callable


優先級

線程的優先級將該線程的重要性傳遞給了調度器。調度器會傾向於讓優先級高的線程先運行。

package concurrency;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by wwh on 16-3-24.
 */
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() {
        return Thread.currentThread() + ": " + countDown;
    }

    public void run() {
        Thread.currentThread().setPriority(priority);
        while (true) {
            /* 這裡只有循環次數比較大才能看出優先級的優勢 */
            for (int i = 0; i < 100000000; ++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();
    }
}

優先級


加入一個線程

一個線程可以在其他線程之上調用 join() 方法,其效果是等待一段時間直到第二個線程結束才繼續執行。join() 調用時可以攜帶超時參數。

package concurrency;

/**
 * Created by wwh on 16-3-24.
 */

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) {
            e.printStackTrace();
            System.out.println(getName() + " was interrupted. " +
            "isInterrupted(): " + isInterrupted());
            return;
        }
        System.out.println(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) {
            e.printStackTrace();
            System.out.println("Interrupted");
        }
        System.out.println(getName() + " join completed");
    }
}

public class Joining {
    public static void main(String[] args) {
        Sleeper
                sleepy = new Sleeper("Sleepy", 1500),
                grumpy = new Sleeper("Grumpy", 1500);
        Joiner
                drpey = new Joiner("Dopey", sleepy),
                doc = new Joiner("Doc", grumpy);
        grumpy.interrupt();

    }
}

共享受限資源

多個線程可能出現訪問共享資源的情況。

package concurrency;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by wwh on 16-4-7.
 */
public class Counter implements Runnable {
    private static int counter = 0;
    public static int getCounter() {
        return counter;
    }
    public void run() {
        for (int i = 0; i < 1000000; ++i) {
            counter++;
        }
    }
    public static void test(int n) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < n; ++i) {
            executorService.execute(new Counter());
        }
        executorService.shutdown();
    }

    public static void main(String[] args) throws InterruptedException {
        Counter.test(10);
        System.out.println(Counter.getCounter());
    }

}

以上代碼沒有進行同步,多個線程同時增加計數器。所以導致結果不正確。

錯誤Java 為我們提供了幾種方式。

synchronzied:包括兩種用法,synchronzied 方法和 synchronized 塊。對於有 synchronzied 關鍵字修飾的類方法或代碼塊,執行時首先要獲取該類實例的鎖,執行完畢後釋放。在執行過程中要有其它線程等請求該 synchronzied 方法或代碼塊則被阻塞。
Lock:互斥鎖,顯示鎖對象。Lock 對象必須被顯式創建、鎖定和釋放。
ReentrantLock:可重入鎖,允許嘗試著去獲取鎖。

這幾種方式後續會詳細解釋。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved