1. 程式人生 > >Java併發學習2--Sychronizer

Java併發學習2--Sychronizer

Synchoronizer
接下來說一下同步輔助類的相關內容
1、閉鎖(Latch)
閉鎖的作用是:延遲執行緒的進度直到執行緒達到終點。
原理:閉鎖相當於一個大門,在大門開啟之前,也就是終點狀態到來之前,沒有一個執行緒能夠通過,都處於阻塞狀態。一旦終點狀態到來,大門開啟,則允許所有執行緒通過。一旦閉鎖達到終點狀態,它就不能改變狀態了。
閉鎖可以用來確保特定活動直到其他活動完成之後才發生。

CountDownLatch
一個閉鎖的實現類,初始化為一個正數。閉鎖的狀態是一個計數器,當計數器=0時,則達到終點狀態。
兩個主要方法,
countDown():計數器-1
await():當計算器!=0時,則該執行緒一直阻塞到計算器=0。或者等待到執行緒終點或者超時。
看程式碼:

package sychrionizer;

import java.util.concurrent.CountDownLatch;

public class LathchTest {
    public long timeTasks(int sThreads , final Runnable task) throws InterruptedException{
        //開始閥門,初始化計數器為1
        final CountDownLatch startGate = new CountDownLatch(1);
        //結束閥門
        final CountDownLatch stopGate = new
CountDownLatch(sThreads); for(int i = 0 ; i < sThreads ; i++){ Thread d = new Thread(){ public void run() { try { System.out.println("執行緒"+ Thread.currentThread().getName() + "等待開始標誌..."); startGate.await
(); try { task.run(); } finally { System.out.println("執行緒"+ Thread.currentThread().getName() + "結束標誌-1"); stopGate.countDown(); } } catch (Exception }; d.start(); } //計時 long start = System.nanoTime(); System.out.println("開始標誌位-1,開發閥門開啟....."); startGate.countDown(); System.out.println("結束標誌阻塞,等待結束閥門..."); stopGate.await(); long stop = System.nanoTime(); return stop - start; } public static void main(String[] args) { final Runnable task = new Runnable() { public void run() { System.out.println("任務"+Thread.currentThread().getName() + "正在執行"); } }; try { System.out.println(new LathchTest().timeTasks(10, task)); } catch (InterruptedException e) { e.printStackTrace(); } } }

FutureTask
也可以作為閉鎖,FutureTask是通過Callable實現的,它等價於一個可攜帶結果的Runnable,FutureTask具有三個狀態,等待、執行、完成。完成狀態包括以任務方式結束,比如正常結束、取消、異常。一旦FutureTask進入完成狀態,FutureTask會永遠停止在這個狀態上面。
主要方法:
get(),依賴於任務的狀態,如果任務完成,則get可以直接獲取到結果。否則,一直等待,知道獲取到結果或者丟擲異常。
來看看小例子

package sychrionizer;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class FutureTaskTest {


    private final FutureTask<String> future = new FutureTask<String>(new Callable<String>() {
        public String call() throws Exception {
            System.out.println("結果執行中....");
            return "結果..是[1231231231]";
        };

    });

    private final Thread thread = new Thread(future);

    /*
     * 對外啟動執行緒的方法 
     */
    public void start(){
        System.out.println("開始計算");
        thread.start();
    }

    /*
     * 對外獲取結果的執行緒
     */
    public String get(){

        try {
            System.out.println(Thread.currentThread().getName() + "執行緒準備獲取結果!");
            return future.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        return null;

    }



    public static void main(String[] args) {
        final FutureTaskTest future = new FutureTaskTest();

        // 開關開啟
        //開始計算
        future.start();

        for(int i = 0 ; i < 10 ; i++){
            Thread t = new Thread(){

                public void run() {
                    System.out.println(future.get());
                }

            };
            t.start();
        }

        // 開關開啟
        //開始計算

        //future.start();

    }
}

測試程式碼有兩種情況,一種是,future.start();寫在上面,執行結果是:

開始計算
結果執行中....
Thread-1執行緒準備獲取結果!
結果..是[1231231231]
Thread-2執行緒準備獲取結果!
結果..是[1231231231]
Thread-3執行緒準備獲取結果!
結果..是[1231231231]
Thread-4執行緒準備獲取結果!
結果..是[1231231231]
Thread-5執行緒準備獲取結果!
結果..是[1231231231]
Thread-6執行緒準備獲取結果!
結果..是[1231231231]
Thread-7執行緒準備獲取結果!
結果..是[1231231231]
Thread-8執行緒準備獲取結果!
結果..是[1231231231]
Thread-9執行緒準備獲取結果!
結果..是[1231231231]
Thread-10執行緒準備獲取結果!
結果..是[1231231231]

這種情況就是先計算出結果,再去get,可以直接get到結果
第二種情況是start在下面,也就是說先get,再計算,輸出是這樣的:

Thread-2執行緒準備獲取結果!
Thread-6執行緒準備獲取結果!
Thread-7執行緒準備獲取結果!
Thread-5執行緒準備獲取結果!
Thread-4執行緒準備獲取結果!
Thread-3執行緒準備獲取結果!
Thread-1執行緒準備獲取結果!
開始計算
Thread-10執行緒準備獲取結果!
Thread-8執行緒準備獲取結果!
Thread-9執行緒準備獲取結果!
結果執行中....
結果..[1231231231]
結果..是[1231231231]
結果..是[1231231231]
結果..是[1231231231]
結果..是[1231231231]
結果..是[1231231231]
結果..是[1231231231]
結果..是[1231231231]
結果..是[1231231231]
結果..是[1231231231]

上面的結果說嗎,get()方法如果獲取不到結果,則一直等待到結果為止。

2、Semaphore(訊號量)

Semaphore用來控制能夠同時訪問某特定資源的活動數量。
Semaphore原理:一個Semaphore管理一個有效的許可集(premit),許可的初始量通過構造方法傳入Semaphore。活動能夠獲得許可(在有剩餘許可的前提下),並且在使用之後釋放改許可。如果沒有許可了,那麼acquire會被阻塞,直到有用的許可為止或者中斷或者操作超時。
主要方法:acquire()請求許可,有空餘,則佔領,否則被阻塞,等待一直有空閒的許可位置
release()釋放許可

舉個簡單的例子,上廁所,共有兩個坑,是個人排隊,一次性只能上兩個人,其餘8人的請求只能等待,一直到有坑位位置。

package sychrionizer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class MySemaphore implements Runnable{

    //測試位置
    private Semaphore position;
    private int id;


    public MySemaphore(Semaphore position , int id){
        this.id = id;
        this.position = position;
    }


    public void run() {
        try {
            if(position.availablePermits() > 0){
                System.out.println("執行緒"+ this.id + "進入,發現有空位");
            }else{
                System.out.println("執行緒"+ this.id + "進入,發現沒有空位");
            }

            //獲取到空位置 
            position.acquire();
            System.out.println("執行緒"+ this.id + "獲得了該位置");
            Thread.sleep(1000);
            System.out.println("執行緒"+ this.id + "使用完畢,離開了該位置");
            position.release();

        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }



    public static void main(String[] args) {

        ExecutorService pool = Executors.newCachedThreadPool();
        Semaphore position = new Semaphore(2);//一共有兩個坑位

        //10
        for(int i = 0 ; i < 10 ; i++){
            pool.submit(new MySemaphore(position, i));
        }
        //釋放執行緒池
        pool.shutdown();
        position.acquireUninterruptibly(2);
        System.out.println("使用完畢,清掃");
        position.release(2);
    }
}

以上例子就說明了訊號量的使用過程。