多線程與高併發學習筆記(二)

本文主要介紹一些Java中常用的同步工具類

ReentrantLock

可重入鎖,可代替synchronized,它比synchronized更加的靈活,提供了更多的方法,但在使用上需要手動的加鎖和釋放鎖;底層使用CAS來實現。使用synchronized鎖定的話如果遇到異常,jvm會自動釋放鎖,但是lock必須手動釋放鎖,因此經常在finally中進行鎖的釋放.

常用方法:
Boolean tryLock() 嘗試獲取鎖,不會阻塞等待 還可加入參數自旋一定時間來獲取鎖
lockinterruptibly() 使用該方法獲取鎖可以讓線程對interrupt() 做出反應。
在創建時可傳遞參數指定爲公平鎖(新線程來了是進入等待隊列還是和已在隊列中的線程一起搶鎖)
具體的使用方法參考 Java API

private Lock lock = new ReentrantLock();
private Condition producer = lock.newCondition();
private Condition consumer = lock.newCondition();

await()  signalAll()

ReentrantLock還支持上面這種用法,newCondition(可以這麼理解,每new一個Condition都相當於多一個等待隊列 ,通過signal() 可以喚醒特定隊列中等待的線程 );在生產者和消費者模式裏,可以利用Condition這一特點,利用signalAll() 來只喚醒對應的生產者 或消費者隊列裏的線程。

CountDownLatch

表面翻譯爲倒數門栓,相當於一個同步計數器(倒數的);創建的時候指定計數器的值,每調用一次countDown() 方法 計數器的值減一,直到計數器值變爲0 在CountDownLatch上等待的線程纔會執行(其他線程做countDown() 等待的線程通過調用await()方法來等待);CountDownLatch是一次性的,計算器的值只能在構造方法中初始化一次。

private static void usingCountDownLatch() {
        Thread[] threads = new Thread[100];
        CountDownLatch latch = new CountDownLatch(threads.length);

        for(int i=0; i<threads.length; i++) {
            threads[i] = new Thread(()->{
                int result = 0;
                for(int j=0; j<10000; j++) result += j;
                latch.countDown();
            });
        }

        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
        }

        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("end latch");
    }

CyclicBarrier

翻譯爲循環障礙,可和CountDownLatch對比理解。它是循環的,可多次使用,是一個循環的障礙。

public static void main(String[] args) {
        //CyclicBarrier barrier = new CyclicBarrier(20);

        CyclicBarrier barrier = new CyclicBarrier(20, () -> System.out.println("通過"));

        /*CyclicBarrier barrier = new CyclicBarrier(20, new Runnable() {
            @Override
            public void run() {
                System.out.println("通過");
            }
        });*/
        for(int i=0; i<100; i++) {

                new Thread(()->{
                    try {
                        barrier.await();

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }).start();
        }
    }

ReadWriteLock

讀寫鎖,爲了提升效率,在讀的時候加讀鎖(共享鎖)多個線程可同時讀,在寫的時候加寫鎖(排它鎖)一次只能有一個線程在寫。
具體的使用如下代碼

public class TestReadWriteLock{
 static Lock lock = new ReentrantLock();
    private static int value;

    static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    static Lock readLock = readWriteLock.readLock();
    static Lock writeLock = readWriteLock.writeLock();

    public static void read(Lock lock) {
        try {
            lock.lock();
            Thread.sleep(1000);
            System.out.println("read over!");
            //模擬讀取操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void write(Lock lock, int v) {
        try {
            lock.lock();
            Thread.sleep(1000);
            value = v;
            System.out.println("write over!");
            //模擬寫操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) {
        //Runnable readR = ()-> read(lock);
        Runnable readR = ()-> read(readLock);
        
        //Runnable writeR = ()->write(lock, new Random().nextInt());
        Runnable writeR = ()->write(writeLock, new Random().nextInt());

        for(int i=0; i<18; i++) new Thread(readR).start();
        for(int i=0; i<2; i++) new Thread(writeR).start();
    }
 }

Phaser

分階段的同步工具, 適用於分階段的任務執行上 第一階段達成目標 然後執行第二階段 第二階段達成目標後 依次繼續 ;使用上需要先繼承Phaser 重寫onAdvance()方法,有一個目標,只有當目標達成纔會執行後續的階段。
具體適用參考如下代碼:

public class TestPhaser {
    static Random r = new Random();
    static MarriagePhaser phaser = new MarriagePhaser();

    static void milliSleep(int milli) {
        try {
            TimeUnit.MILLISECONDS.sleep(milli);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
		// 本例中的目標 
        phaser.bulkRegister(5);

        for(int i=0; i<5; i++) {
            final int nameIndex = i;
            new Thread(()->{

                Person p = new Person("person " + nameIndex);
                p.arrive();
                //本例中沒調用一次register多1 只有達到5纔會執行後面的操作
                phaser.arriveAndAwaitAdvance();

                p.eat();
                phaser.arriveAndAwaitAdvance();

                p.leave();
                phaser.arriveAndAwaitAdvance();
            }).start();
        }

    }

    static class MarriagePhaser extends Phaser {
        @Override
        protected boolean onAdvance(int phase, int registeredParties) {
			//自定義的階段,本例分3個階段,第一次達到執行 case 0 依次類推
            switch (phase) {
                case 0:
                    System.out.println("所有人到齊了!");
                    return false;
                case 1:
                    System.out.println("所有人吃完了!");
                    return false;
                case 2:
                    System.out.println("所有人離開了!");
                    System.out.println("婚禮結束!");
                    return true;
                default:
                    return true;
            }
        }
    }

    static class Person {
        String name;

        public Person(String name) {
            this.name = name;
        }

        public void arrive() {
            milliSleep(r.nextInt(1000));
            System.out.printf("%s 到達現場!\n", name);
        }

        public void eat() {
            milliSleep(r.nextInt(1000));
            System.out.printf("%s 吃完!\n", name);
        }

        public void leave() {
            milliSleep(r.nextInt(1000));
            System.out.printf("%s 離開!\n", name);
        }
    }
}

semaphore

信號量,相當於一個令牌,持令牌者可執行,令牌可申請,用完之後需要釋放。
具體代碼如下:

public class TestSemaphore {
    public static void main(String[] args) {    
    	//創建時初始化兩個令牌,公平申請模式 默認非公平    
        Semaphore s = new Semaphore(2, true);
        //允許一個線程同時執行
        //Semaphore s = new Semaphore(1);

        new Thread(()->{
            try {
                s.acquire();

                System.out.println("T1 running...");
                Thread.sleep(200);
                System.out.println("T1 running...");

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                s.release();
            }
        }).start();

        new Thread(()->{
            try {
            	//申請
                s.acquire();
                System.out.println("T2 running...");
                Thread.sleep(200);
                System.out.println("T2 running...");
				//釋放
                s.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

Exchanger

交換器,用於兩個線程交換數據,可這樣理解,Exchange相當於一個容器,一個線程調用exchange方法時會阻塞等待,當另一個線程也掉了exchange方法後,進入該容器交換二者數據,然後返回。
具體使用參考如下:

public class TestExchanger {

    static Exchanger<String> exchanger = new Exchanger<>();

    public static void main(String[] args) {
        new Thread(()->{
            String s = "data1";
            try {
                s = exchanger.exchange(s);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " " + s);

        }, "t1").start();

        new Thread(()->{
            String s = "data2";
            try {
                s = exchanger.exchange(s);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " " + s);

        }, "t2").start();
    }
}

LockSupport

該工具類可以指定特定線程停止與開始。
具體使用參考如下:

public class T13_TestLockSupport {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println(i);
                if(i == 5) {
                	//停止當前線程的執行
                    LockSupport.park();
                }
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        t.start();
		//剛開始就執行恢復執行,會使得線程不會停止,相當於作廢停止
        LockSupport.unpark(t);

        /*
        try {
            TimeUnit.SECONDS.sleep(8);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("after 8 senconds!");
        //讓停止的t線程恢復執行
        LockSupport.unpark(t);
        */

    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章