實戰Java高併發程序設計-04 Java併發包鎖和其他工具的使用

併發包(java.util.concurrent)

爲了更好的支持併發程序,jdk內部提供了大量實用的API和框架

同步控制(鎖)

鎖(lock)可以完全代替關鍵字synchronize.
jdk中鎖是一個接口,提供了三個實現類,讀鎖,寫鎖,重入鎖.
這裏寫圖片描述

讀寫鎖

讀寫鎖拆成讀鎖和寫鎖來理解。讀鎖可以共享,多個線程可以同時擁有讀鎖,但是寫鎖卻只能只有一個線程擁有,而且獲取寫鎖的時候其他線程都已經釋放了讀鎖,而且該線程獲取寫鎖之後,其他線程不能再獲取讀鎖。簡單的說就是寫鎖是排他鎖,讀鎖是共享鎖。

下面代碼開啓了10個讀取線程,10個寫線程

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockTest {
    private ReadWriteLock lock = new ReentrantReadWriteLock();

    public void read() {
        try {
            lock.readLock().lock();
            System.out.println(Thread.currentThread().getName() + "  開始讀取");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + "  讀取完畢");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
    }

    public void write() {
        try {
            lock.writeLock().lock();
            System.out.println(Thread.currentThread().getName() + "  開始寫數據");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "  寫數據完畢");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }

    public static void main(String[] args) {
        final ReadWriteLockTest rwlt = new ReadWriteLockTest();
        for(int i = 0 ;i < 10 ; i ++){
            new Thread() {
                @Override
                public void run() {
                    rwlt.read();
                }
            }.start();
            new Thread() {
                @Override
                public void run() {
                    rwlt.write();
                }
            }.start();
        }
    }
}

打印結果

Thread-0  開始讀取
Thread-0  讀取完畢
Thread-1  開始寫數據
Thread-1  寫數據完畢
Thread-3  開始寫數據
Thread-3  寫數據完畢
Thread-2  開始讀取
Thread-4  開始讀取
Thread-2  讀取完畢
Thread-4  讀取完畢
Thread-5  開始寫數據
Thread-5  寫數據完畢
Thread-6  開始讀取
Thread-6  讀取完畢
Thread-7  開始寫數據
Thread-7  寫數據完畢
Thread-8  開始讀取
Thread-8  讀取完畢
Thread-9  開始寫數據
Thread-9  寫數據完畢
Thread-10  開始讀取
Thread-10  讀取完畢
Thread-11  開始寫數據
Thread-11  寫數據完畢
Thread-12  開始讀取
Thread-12  讀取完畢
Thread-13  開始寫數據
Thread-13  寫數據完畢
Thread-14  開始讀取
Thread-14  讀取完畢
Thread-15  開始寫數據
Thread-15  寫數據完畢
Thread-16  開始讀取
Thread-16  讀取完畢
Thread-17  開始寫數據
Thread-17  寫數據完畢
Thread-18  開始讀取
Thread-18  讀取完畢
Thread-19  開始寫數據
Thread-19  寫數據完畢

重入鎖

可重入鎖的概念是自己可以再次獲取自己的內部鎖。舉個例子,比如一條線程獲得了某個對象的鎖,此時這個對象鎖還沒有釋放,當其再次想要獲取這個對象的鎖的時候還是可以獲取的(如果不可重入的鎖的話,此刻會造成死鎖)。說的更高深一點可重入鎖是一種遞歸無阻塞的同步機制。

這裏寫圖片描述

//打印類

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Print {
    private Lock lock = new ReentrantLock();
    public void prt(){
        lock.lock();
        for(int i = 0 ; i < 5  ; i++){
            System.out.println(Thread.currentThread().getName() + " : " + i);
        }
        lock.unlock();
    }
}
//線程類
public class Th1 implements Runnable{

    private Print p;

    public Th1(Print p) {
        this.p = p;
    }

    @Override
    public void run() {
        p.prt();
    }
}
//Main方法類
public class M {

    public static void main(String[] args) {

        Print p = new Print();
        new Thread(new Th1(p)).start();
        new Thread(new Th1(p)).start();
    }

}

在重入鎖中提供了公平鎖和非公平鎖 構造的時候傳入參數 true/false 來區分

公平鎖與非公平鎖

公平表示線程獲取鎖的順序是按照線程加鎖的順序來分配的,即先來先得的FIFO順序。而非公平就是一種獲取鎖的搶佔機制,和公平相對就是先來不一定先得,這個方式可能造成某些線程飢餓(一直拿不到鎖)

Condition(併發包中的wait與notify)

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Condition 配合Lock  實現線程的等待 與通知
 */
public class ConditionTest{
    public static ReentrantLock lock=new ReentrantLock();
    public static Condition condition =lock.newCondition();
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                lock.lock();//請求鎖
                try{
                    System.out.println(Thread.currentThread().getName()+"==》進入等待");
                    condition.await();//設置當前線程進入等待
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }finally{
                    lock.unlock();//釋放鎖
                }
                System.out.println(Thread.currentThread().getName()+"==》繼續執行");
            }    
        }.start();
        new Thread(){
            @Override
            public void run() {
                lock.lock();//請求鎖
                try{
                    System.out.println(Thread.currentThread().getName()+"=》進入");
                    Thread.sleep(2000);//休息2秒
                    condition.signal();//隨機喚醒等待隊列中的一個線程
                    System.out.println(Thread.currentThread().getName()+"休息結束");
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }finally{
                    lock.unlock();//釋放鎖
                }
            }    
        }.start();
    }
}

Semaphore(信號量)

信號量爲多線程提供了更爲強大的控制方法,無論是鎖還是synchronize,一次都只允許一個線程訪問一個資源,而信號量可以指定多少個線程,同時訪問某一個資源.

通過 acquire() 獲取一個許可,如果沒有就等待,而 release() 釋放一個許可,

//打印類
import java.util.concurrent.Semaphore;

public class Print {

    private Semaphore semaphore = new Semaphore(5);

    public void prt(){
        try {
            System.out.println(Thread.currentThread().getName() + " : 準備進入" );
            semaphore.acquire();
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName() + " : 進入" );
            Thread.sleep(3000);
            semaphore.release();
            System.out.println(Thread.currentThread().getName() + " : 離開" );
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
//線程類
public class Th1 implements Runnable{

    private Print p;

    public Th1(Print p) {
        this.p = p;
    }

    @Override
    public void run() {
        p.prt();
    }
}
//Main方法類
public class M {

    public static void main(String[] args) {

        Print p = new Print();
        for(int i = 0 ; i < 14 ; i++){
            new Thread(new Th1(p)).start();
        }
    }

}

計數器(CountDownLatch)

主線程模擬裁判,八個子線程模擬運動員,裁判和八個運動員都就位以後,運動員才能開始跑步

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CountDownTest {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newCachedThreadPool();
        final CountDownLatch cdl1 = new CountDownLatch(1);//裁判吹哨開始倒計時,歸零運動員開始跑步
        final CountDownLatch cdl2 = new CountDownLatch(8);//運動員跑完了,裁判公佈結果。//cdl2.countDown() 這個方法 每次到達一個會減少1  而不是一次減完  這裏八個運動員跑步 所以這裏  實例化對象的 參數 應該是  8
        final Map<String , Long> map = new HashMap<String , Long>();
        for(int i =0 ; i < 8 ;i ++){//八個運動員
            pool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("運動員 " + Thread.currentThread().getName() + " 就位");
                    try {
                        cdl1.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("運動員:" + Thread.currentThread().getName() + "開始跑步");
                    try {
                        Thread.sleep(new Random().nextInt(10) * 1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    map.put(Thread.currentThread().getName(), new Date().getTime());
                    System.out.println("運動員:" + Thread.currentThread().getName() + " 跑完");
                    cdl2.countDown();
                }
            });
        }
        long l = 0;
        try {
            System.out.println("裁判就位");
            Thread.sleep(3000);
            cdl1.countDown();
            System.out.println("裁判發了起跑消息,運動員起跑 , 裁判等待跑完");
            l = new Date().getTime();
            cdl2.await();
            System.out.println("跑完了,裁判發佈結果");
            System.out.println("結果如下");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        pool.shutdown();

        for(Entry<String, Long> entry : map.entrySet()){
            System.out.println(entry.getKey() + " 所用所用時間是 :"
                    + (entry.getValue() - l));
        }
    }
}
運動員 pool-1-thread-1 就位
運動員 pool-1-thread-4 就位
運動員 pool-1-thread-3 就位
運動員 pool-1-thread-2 就位
裁判就位
運動員 pool-1-thread-6 就位
運動員 pool-1-thread-7 就位
運動員 pool-1-thread-5 就位
運動員 pool-1-thread-8 就位
裁判發了起跑消息,運動員起跑 , 裁判等待跑完
運動員:pool-1-thread-1開始跑步
運動員:pool-1-thread-3開始跑步
運動員:pool-1-thread-2開始跑步
運動員:pool-1-thread-4開始跑步
運動員:pool-1-thread-8開始跑步
運動員:pool-1-thread-5開始跑步
運動員:pool-1-thread-7開始跑步
運動員:pool-1-thread-6開始跑步
運動員:pool-1-thread-3 跑完
運動員:pool-1-thread-5 跑完
運動員:pool-1-thread-7 跑完
運動員:pool-1-thread-6 跑完
運動員:pool-1-thread-2 跑完
運動員:pool-1-thread-8 跑完
運動員:pool-1-thread-1 跑完
運動員:pool-1-thread-4 跑完
跑完了,裁判發佈結果
結果如下
pool-1-thread-4 所用所用時間是 :7999
pool-1-thread-5 所用所用時間是 :2999
pool-1-thread-2 所用所用時間是 :5998
pool-1-thread-3 所用所用時間是 :998
pool-1-thread-1 所用所用時間是 :7998
pool-1-thread-7 所用所用時間是 :2999
pool-1-thread-6 所用所用時間是 :3999
pool-1-thread-8 所用所用時間是 :5999

CyclicBarrier(循環柵欄)

循環柵欄與計算器很像,但是可以反覆使用,下面模擬 十個人一起去景點的場景.

//線程類
package c;

import java.util.Random;
import java.util.concurrent.CyclicBarrier;

public class Th1 implements Runnable{

    private CyclicBarrier cb;

    public Th1(CyclicBarrier cb2) {
        this.cb = cb2;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(new Random().nextInt(10) * 1000);
            System.out.println(Thread.currentThread().getName() + "到了黃鶴樓 已經有:" + (cb.getNumberWaiting() + 1) + "個到達黃鶴樓");
            if(cb.getNumberWaiting() == 10){
                System.out.println("全部到齊,下一站龜山");
            }
            cb.await();

            Thread.sleep(new Random().nextInt(10) * 1000);
            System.out.println(Thread.currentThread().getName() + "到了龜山 已經有:" + (cb.getNumberWaiting() + 1) + "個到達龜山");
            if(cb.getNumberWaiting() == 10){
                System.out.println("全部到齊,下一站東湖");
            }
            cb.await();

            Thread.sleep(new Random().nextInt(10) * 1000);
            System.out.println(Thread.currentThread().getName() + "到了東湖 已經有:" + (cb.getNumberWaiting() + 1) + "個到達東湖");
            if(cb.getNumberWaiting() == 10){
                System.out.println("全部到齊,結束");
            }
            cb.await();
        } catch (Exception e1) {
            e1.printStackTrace();
        }
    }
}
//Main方法類
package c;

import java.util.concurrent.CyclicBarrier;

public class M {

    public static void main(String[] args) {

        CyclicBarrier cb = new CyclicBarrier(10);

        for(int i = 0 ; i < 10 ; i++){
            new Thread(new Th1(cb)).start();
        }

    }

}

打印結果

Thread-3到了黃鶴樓 已經有:1個到達黃鶴樓
Thread-7到了黃鶴樓 已經有:2個到達黃鶴樓
Thread-8到了黃鶴樓 已經有:3個到達黃鶴樓
Thread-9到了黃鶴樓 已經有:4個到達黃鶴樓
Thread-1到了黃鶴樓 已經有:5個到達黃鶴樓
Thread-5到了黃鶴樓 已經有:6個到達黃鶴樓
Thread-2到了黃鶴樓 已經有:7個到達黃鶴樓
Thread-0到了黃鶴樓 已經有:7個到達黃鶴樓
Thread-6到了黃鶴樓 已經有:9個到達黃鶴樓
Thread-4到了黃鶴樓 已經有:10個到達黃鶴樓
Thread-6到了龜山 已經有:1個到達龜山
Thread-1到了龜山 已經有:1個到達龜山
Thread-5到了龜山 已經有:3個到達龜山
Thread-3到了龜山 已經有:4個到達龜山
Thread-8到了龜山 已經有:4個到達龜山
Thread-2到了龜山 已經有:6個到達龜山
Thread-4到了龜山 已經有:7個到達龜山
Thread-9到了龜山 已經有:8個到達龜山
Thread-7到了龜山 已經有:9個到達龜山
Thread-0到了龜山 已經有:10個到達龜山
Thread-3到了東湖 已經有:1個到達東湖
Thread-0到了東湖 已經有:2個到達東湖
Thread-8到了東湖 已經有:3個到達東湖
Thread-4到了東湖 已經有:4個到達東湖
Thread-2到了東湖 已經有:4個到達東湖
Thread-5到了東湖 已經有:4個到達東湖
Thread-6到了東湖 已經有:7個到達東湖
Thread-1到了東湖 已經有:7個到達東湖
Thread-9到了東湖 已經有:9個到達東湖
Thread-7到了東湖 已經有:10個到達東湖
發佈了87 篇原創文章 · 獲贊 3 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章