1. 程式人生 > 實用技巧 >JDK併發包一

JDK併發包一

JDK併發包一

同步控制是併發程式必不可少的重要手段,synchronized是一種最簡單的控制方法。但JDK提供了更加強大的方法————重入鎖

重入鎖

重入鎖使用java.util.concurrent.locks.ReentrantLock類來實現。

ReentrantLock有幾個重要的方法

  • lock()獲得鎖,如果鎖已佔用,則等待。
  • lockInterruptibly()獲得鎖,優先響應中斷。
  • tryLock() 嘗試獲得鎖若成功返回true 失敗返回false 不等待立即返回
  • tryLock(long time,TimeUnit unit) 在給定的時間內進行等待
  • unlock()釋放鎖
public class MyTest {

    public static ReentrantLock lock = new ReentrantLock();
    public static int i = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new ReenterLock());
        Thread thread2 = new Thread(new ReenterLock());

        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(i);
    }


    public static class ReenterLock implements Runnable {

        @Override
        public void run() {
            for (int j = 0; j < 1000000; j++) {
                lock.lock();
                try {
                    i++;
                } finally {
                    lock.unlock();
                }


            }
        }
    }

}

與synchronized相比重入鎖更加靈活 一個執行緒可以多次獲取鎖,獲取多少次一定要釋放多少次

中斷響應

對於synchronized相比如果一個執行緒等待鎖,只有兩種情況,要麼獲得鎖繼續執行,要麼等待。重入鎖可以有另一種情況————中斷thread2.interrupt()

 /**
     * 中斷響應
     */
    public static class IntLock implements Runnable {
        int lock;
        public static ReentrantLock lock1=new ReentrantLock();
        public static ReentrantLock lock2=new ReentrantLock();

        public IntLock(int lock) {
            this.lock = lock;
        }


        @Override
        public void run() {
            try {

                if (lock==1){
                    lock1.lockInterruptibly();
                    Thread.sleep(500);
                    lock2.lockInterruptibly();
                }else {
                    lock2.lockInterruptibly();
                    Thread.sleep(500);
                    lock1.lockInterruptibly();
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                if (lock1.isHeldByCurrentThread())
                lock1.unlock();
                if (lock2.isHeldByCurrentThread())
                lock2.unlock();
                System.out.println(Thread.currentThread().getName()+":執行緒退出");
            }

        }
    }


    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new IntLock(1));
        Thread thread2 = new Thread(new IntLock(2));
        thread1.start();
        thread2.start();
        Thread.sleep(2000);
        thread2.interrupt();


    }

thread2獲得了thread1的鎖thread1獲得了thread2的鎖兩個執行緒相互等待。 程式形成死鎖thread2.interrupt()執行後thread2中斷 自動釋放獲得的所有鎖,thread1可以繼續執行。真正完成的只有thread1

鎖申請等待限時

除了上面中斷通過外部通知的方式避免死鎖還有另一種方法,限時等待tryLock()

tryLock()方法有兩個引數一個表示等待時長,一個是計時單位,在設定的時間內執行緒會請求獲取鎖,超過設定的時間還沒獲得鎖就會返回false。獲得成功就返回true

公平鎖

在大多數情況下鎖都是非公平的,即執行緒不是按照請求在鎖不可用時鎖的先後順序獲得鎖的,系統只會從等待的執行緒中隨機選一個,而公平鎖會按照請求順序在鎖可用是分配鎖。ReentrantLock lock1=new ReentrantLock(true);建立鎖時在構造方法傳入true時此鎖就是公平鎖,公平鎖需要一個有序佇列來實現因此成本較高效能低下沒特殊要求一般不用。

Condition條件

使用synchronized進行通知就使用Object.wait() ,Object.notify() ,使用鎖時Condition的方法幾乎相同。

  • await()方法會使當前執行緒等待,同時釋放當前鎖,當其他執行緒中使用signal()或者signalAll()方法時執行緒會重現獲得鎖繼續執行,當執行緒中斷時也能跳出等待
  • awaitUninterruptibly()方法與await()基本相同,但是它並不會在等待過程中響應中斷
  • singnal()方法用於喚醒一個等待中的執行緒,signalAll()喚醒所有執行緒
	public static ReentrantLock lock = new ReentrantLock();
    public static Condition condition = lock.newCondition();

    public static void main(String[] args) {
        Thread thread = new Thread(new ReenterLockCondition());
        thread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        lock.lock();
        condition.signal();
        lock.unlock();

    }

    public static class ReenterLockCondition implements Runnable {

        @Override
        public void run() {

            try {
                lock.lock();
                condition.await();
                System.out.println("執行緒開始執行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }

        }
    }

在執行Conidition.await()之前要求當前執行緒必須持有相關的重入鎖,執行signal()也一樣也需要先持有重入鎖,在singnal()執行後一般需要釋放相關重入鎖,否則喚醒的執行緒無法重新獲得相關重入鎖無法繼續執行。

讀寫鎖

讀寫鎖是分離讀操作與寫(修改,刪除,新增)的鎖,可以更大程度提高效能。

非阻塞 阻塞
阻塞 阻塞

如果一個系統中有大量的讀操作,那麼讀寫鎖就能發揮更大功效。

    public static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();//讀寫鎖
    public static Lock read = readWriteLock.readLock();//讀鎖
    public static Lock write=readWriteLock.writeLock();//寫鎖

倒計時器

對於某個執行緒需要等待其他幾個執行緒執行完畢自己再執行比較適用

假如我們這個想要繼續往下執行的任務呼叫一個CountDownLatch物件的await()方法,其他的任務執行完自己的任務後呼叫同一個CountDownLatch物件上的countDown()方法,這個呼叫await()方法的任務將一直阻塞等待,直到這個CountDownLatch物件的計數值減到0為止。

    public static final CountDownLatch end = new CountDownLatch(10);

    public static void main(String[] args) {

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            executorService.execute(new CountDownLatchDemo());
        }
        try {
            end.await();
            System.out.println("準備完畢");
            executorService.shutdown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    public static class CountDownLatchDemo implements Runnable {

        @Override
        public void run() {
            try {
                Thread.sleep(new Random().nextInt(10)*1000);
                System.out.println(Thread.currentThread().getName()+"準備完畢");
                end.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

迴圈柵欄

CyclicBarrier實現的功能與CountDownLatch類似但剛強大,CyclicBarrier可以實現計數器的複用,

public class Soldier implements Runnable{


    private String soldier;
    private CyclicBarrier cyclicBarrier;


    public Soldier(String soldier, CyclicBarrier cyclicBarrier) {
        this.soldier = soldier;
        this.cyclicBarrier = cyclicBarrier;
    }

   public void doWork(){
       try {
           Thread.sleep(Math.abs(new Random().nextInt()%10000));

       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       System.out.println(this.soldier+"任務完成");
   }

    @Override
    public void run() {
        try {
            this.cyclicBarrier.await();
            doWork();
            this.cyclicBarrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

public class BarrierRun implements Runnable {

    private boolean flag;
    private int i;

    public BarrierRun(boolean flag, int i) {
        this.flag = flag;
        this.i = i;
    }

    @Override
    public void run() {
        if (this.flag) {
            System.out.println("任務完成");
        }else {
            System.out.println("集合完畢");
        }
    }
}

   public static void main(String[] args) {
        Thread[] threads=new Thread[10];
        CyclicBarrier cyclicBarrier = new CyclicBarrier(10, new BarrierRun(false, 10));
        System.out.println("隊伍集合");
        for (int i = 0; i < 10; i++) {
            System.out.println("士兵"+i+"報道");
            threads[i]=new Thread(new Soldier("士兵"+i,cyclicBarrier));
            threads[i].start();
        }
    }