併發程式設計13

Markland_l發表於2020-12-17

併發程式設計13

高效能讀寫鎖StampedLock(jdk內部的)

  • ReentrantReadWriteLock 的效能已經很好了但是他底層還是需要進行一系列的cas操作去加鎖;
    StampedLock如果是讀鎖上鎖是沒有這種cas操作的效能比ReentrantReadWriteLock 更好
  • 也稱為樂觀讀鎖;即讀獲取鎖的時候 是不加鎖 直接返回一個值;然後執行臨界區的時候去驗證這個值是否有被人修改(寫操作加鎖)
  • 如果沒有被人修改則直接執行臨界區的程式碼;如果被人修改了則需要升級為讀寫鎖
    (ReentrantReadWriteLock—>readLock);

基本語法

  • //獲取戳 不存在鎖
    long stamp = lock.tryOptimisticRead();
    //驗證戳
    if(lock.validate(stamp)){
    	//驗戳成立則執行臨界區的程式碼
    	//返回
    }
    //如果沒有返回則表示被人修改了 需要升級成為readLock
    lock.readLock();
    

兩把讀鎖

  • package BingFaBianCheng.bingFaBianCheng13.shadow.stampedLock;
    
    import java.util.concurrent.TimeUnit;
    
    public class StampedLockTest {
    
        public static void main(String[] args) throws InterruptedException {
            //例項化資料容器
            DataContainer dataContainer = new DataContainer();
            //給了一個初始值  不算寫 構造方法賦值
            dataContainer.setI(1);
    
            //讀取
            new Thread(() -> {
                dataContainer.read();
            }, "t1").start();
    
    		// 第二個執行緒也去讀取
            new Thread(() -> {
                dataContainer.read();
            }, "t2").start();
        }
    }
    
    
  • package BingFaBianCheng.bingFaBianCheng13.shadow.stampedLock;
    
    import lombok.SneakyThrows;
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.Random;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.StampedLock;
    
    /**
     * 一個資料容器
     * 不支援重入
     * 不支援條件
     */
    @Slf4j(topic = "enjoy")
    public class DataContainer {
        int i;
        long stampw=0l;
    
        public void setI(int i) {
            this.i = i;
        }
    
        private final StampedLock lock = new StampedLock();
    
        //首先 加 StampedLock
        @SneakyThrows
        public int read() {
            //嘗試一次樂觀讀
            long stamp = lock.tryOptimisticRead();
    
            log.debug("StampedLock 讀鎖拿到的戳{}", stamp);
            //1s之後驗戳
            //TimeUnit.SECONDS.sleep(1);
            //驗戳
            if (lock.validate(stamp)) {
                //執行緒安全問題
                // 這裡怎麼都不互斥,因為是無鎖和寫鎖不互斥
                log.debug("StampedLock 驗證完畢stamp{}, data.i:{}", stamp, i);
                TimeUnit.SECONDS.sleep(10);
                return i;
            }
            //一定驗證失敗
            log.debug("驗證失敗 被寫執行緒給改變了{}", stampw);
            try {
                //鎖的升級 也會改戳
                stamp = lock.readLock();
                log.debug("升級之後的加鎖成功 {}", stamp);
                TimeUnit.SECONDS.sleep(1);
                log.debug("升級讀鎖完畢{}, data.i:{}", stamp, i);
                return i;
            } finally {
                log.debug("升級鎖解鎖 {}", stamp);
                lock.unlockRead(stamp);
            }
        }
    
    
    
        @SneakyThrows
        public void write(int i) {
            //cas 加鎖
            stampw = lock.writeLock();
            log.debug("寫鎖加鎖成功 {}", stampw);
            try {
                TimeUnit.SECONDS.sleep(5);
                this.i = i;
            } finally {
                log.debug("寫鎖解鎖 {},data.i{}", stampw,i);
                lock.unlockWrite(stampw);
            }
        }
    }
    
    

加寫鎖

  • package BingFaBianCheng.bingFaBianCheng13.shadow.stampedLock;
    
    import java.util.concurrent.TimeUnit;
    
    public class StampedLockTest {
    
        public static void main(String[] args) throws InterruptedException {
            //例項化資料容器
            DataContainer dataContainer = new DataContainer();
            //給了一個初始值  不算寫 構造方法賦值
            dataContainer.setI(1);
    
            //讀取
            new Thread(() -> {
                dataContainer.read();
            }, "t1").start();
    
    
    //        new Thread(() -> {
    //            dataContainer.read();
    //        }, "t2").start();
    
            TimeUnit.SECONDS.sleep(3);
            new Thread(() -> {
                dataContainer.write(9);
            }, "t2").start();
        }
    }
    
    

那麼StampedLock的效能這麼好能否替代ReentrantReadWriteLock ?

  • 1、他不支援重入
  • 2、不支援條件佇列
  • 3、存在一定的併發問題

samephore

  • 來限制對資源訪問的執行緒的上限,而不是資源的上線,除非執行緒和資源是一一對應的;好比洗浴店裡面的手牌,比如你進去一個洗浴店裡,服務生首先會給你一個手牌;如果手牌沒有了你則需要去喝茶等待;等他其他問洗完你才可以去享受服務;手牌相當於你一個許可;你去享受服務的時候先要獲取手牌,服務完成之後需要歸還手牌;

  • 比如mysql的連線池就可以,一次只能拿到1個連線;Tomcat就不行,一次可以拿到多個資源

示例

  • package BingFaBianCheng.bingFaBianCheng13.shadow.semaphore;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.*;
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * 來限制對資源訪問的執行緒上線
     */
    @Slf4j(topic = "enjoy")
    public class SemaphoreTest {
        public static void main(String[] args) {
    
            //每次訪問的執行緒上限是3
            Semaphore semaphore = new Semaphore(3);
    
            for (int i = 0; i < 15; i++) {
                new Thread(() -> {
                    try {
                        // 獲取許可
                        semaphore.acquire();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    try {
                        log.debug("start...");
                        // 睡眠1秒鐘
                        // 每一秒只有3個許可
                        TimeUnit.SECONDS.sleep(1);
                        log.debug("end...");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        // 釋放 +(之前釋放都是 -)
                        semaphore.release();
                    }
                }).start();
            }
        }
    }
    
    

獲取許可

  • 上面前三個執行緒都會順利拿到鎖,因為tryAcquireShared(arg) >=0
  • 第四個執行緒來了,不滿足上面的條件,會執行doAcquireSharedInterruptibly(arg)
  • 形成aqs佇列

解釋

  • Node head ->{thread=null prev=null next=t4 ws=-1}
    Node tail ->{thread=t4 pre=head next=null ws=0}
    exclusiveOwnerThread:t1、t2、t3
    state:0
    

釋放許可

  • 先執行tryReleaseShared方法,取出state,通過cas加1,如果成功,則釋放成功
  • 如果上一步釋放鎖成功,則繼續執行doReleaseShared方法,喚醒aqs佇列

總結

  • samephore給這把鎖的state賦了一個初值,限定了同時可以獲取鎖的執行緒數目(同時持有並且都沒是方法)
  • ReentrantLock初始值是0,加鎖成功會加1,1表示的是鎖被佔有,剛好與samephore相反

CountDownLatch

  • 倒數計時鎖;某個執行緒x等待倒數計時為0的時候才執行;所謂的倒數計時其實就是一個int型別的變數,在初始化CountDownLatch的時候會給他一個初始值(程式設計師定的);在多執行緒工作的時候可以通過countDown()方法來對計數器-1;當等於0的時候x則會解阻塞執行

基本語法

  • //初始化物件,給一個初始值
    CountDownLatch latch = new CountDownLatch(3);
    //x執行緒 呼叫await阻塞 等待計數器為0的時候才會解阻塞
    latch.await();
    //其他執行緒呼叫countDown();對計數器-1
    latch.countDown();
    

通過new thread方法獲取新執行緒

  • package BingFaBianCheng.bingFaBianCheng13.shadow.countDownLatch;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.TimeUnit;
    
    @Slf4j(topic = "enjoy")
    public class CountDownLatchTest1 {
    
        public static void main(String[] args) throws InterruptedException {
             // ExecutorService executorService = Executors.newFixedThreadPool(3);
            // executorService
    
    
            //計數器=3
            CountDownLatch latch = new CountDownLatch(3);
    
    
    
            Thread thread = new Thread(() -> {
                log.debug("t1 thread start");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //t1 把計數器-1
                latch.countDown();
                log.debug("t1 thread end;count[{}]", latch.getCount());
            }, "t1");
            thread.start();
    
            new Thread(() -> {
                log.debug("t2 thread start");
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                latch.countDown();
                log.debug("t2 thread end;count[{}]", latch.getCount());
            },"t2").start();
    
            new Thread(() -> {
                log.debug("t3 thread start");
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                latch.countDown();//主執行緒可以執行了
                log.debug("t3 thread end;count[{}]", latch.getCount());
    
            },"t3").start();
    
    
    
            log.debug("main watiing");
            latch.await();
    
            log.debug("main wait end...");
        }
    }
    
    

countDownLatch和thread.join有什麼區別

  • thread.join阻塞的是執行這句的執行緒
  • 1.前者可以很方便的配合執行緒池使用,因為實際寫程式碼不可能通過new thread這種方法來建立執行緒
  • 2.join是一定等待執行緒執行完軟方法才解阻塞,而countDownLatch在latch.countDown()方法之後就解阻塞了,該方法之後的程式碼有可能和主執行緒同時執行

執行緒池初始化執行緒

  • package BingFaBianCheng.bingFaBianCheng13.shadow.countDownLatch;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.*;
    import java.util.concurrent.atomic.AtomicInteger;
    
    @Slf4j(topic = "enjoy")
    public class CountDownLatchTest2 {
    
        public static void main(String[] args) throws InterruptedException {
            //執行緒池裡面建立4個執行緒  其中三個是計算的  第四個是彙總的
            AtomicInteger i= new AtomicInteger();
            ExecutorService executorService = Executors.newFixedThreadPool(4,(e)-> new Thread(e,"t"+i.incrementAndGet()));
    
            CountDownLatch latch = new CountDownLatch(3);
    
    
            executorService.submit(()->{
                log.debug("t1 thread start");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                latch.countDown();
                log.debug("t1 thread end;count[{}]", latch.getCount());
            });
    
    
    
            executorService.submit(()->{
                log.debug("t2 thread start");
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                latch.countDown();
                log.debug("t2 thread end;count[{}]", latch.getCount());
            });
    
            executorService.submit(()->{
                log.debug("t3 thread start");
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                latch.countDown();
                log.debug("t3 thread end;count[{}]", latch.getCount());
            });
    
    
            executorService.submit(()->{
                log.debug("t4 watiing");
                try {
                    latch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("t4 wait end...");
    
            });
    
            executorService.shutdown();
        }
    }
    
    
  • 執行緒池使用完之後,要注意shutdown()

  • 其實前三個方法內容都是一樣的,可以用一個for迴圈來精簡程式碼

  • //執行緒池裡面建立4個執行緒  其中三個是計算的  第四個是彙總的
    AtomicInteger i= new AtomicInteger();
    // 通過ThreadFactory來執行執行緒中執行緒的名字
    ExecutorService executorService = Executors.newFixedThreadPool(4,(e)->{
        return  new Thread(e,"t"+i.incrementAndGet());
    });
    

模擬四個子執行緒執行不同的工作,主執行緒等所有子執行緒執行完後才繼續往下執行

  • package BingFaBianCheng.bingFaBianCheng13.shadow.countDownLatch;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Random;
    import java.util.concurrent.*;
    import java.util.concurrent.atomic.AtomicInteger;
    
    @Slf4j(topic = "enjoy")
    public class CountDownLatchTest3 {
    
        public static void main(String[] args) throws InterruptedException {
            //執行緒池裡面建立3個執行緒
            List<String> list = new ArrayList<>();
            list.add("Angel");
            list.add("baby");
            list.add("rose");
            list.add("joyce");
    
            AtomicInteger i= new AtomicInteger();
            ExecutorService executorService = Executors.newFixedThreadPool(4,(runnable)->{
                //技師的名字
                return new Thread(runnable,list.get(i.getAndIncrement()));
            });
    
            //讓你先去沙發上休息
            CountDownLatch latch = new CountDownLatch(4);
            Random random = new Random();
            for (int j = 0; j <4 ; j++) {//new 4個執行緒  併發執行
                int temp =j;
                executorService.submit(()->{
                    //k標識的是準備進度 直到準備到100% 才開始服務  這個時間每個技師不固定  因為是random
                    for (int k = 0; k <100 ; k++) {
                        try {
                            //模擬每一個技師準備的時間
                            TimeUnit.MILLISECONDS.sleep(random.nextInt(200));
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                        String name = Thread.currentThread().getName();
                        name=name+"("+k+"%)";//angel(3%) baby(10%) ...
                        list.set(temp,name);
                        System.out.print("\r"+Arrays.toString(list.toArray()));
                    }
                    //某個人準備好了
                    latch.countDown();
                });
            }
            latch.await();
            System.out.println("\n 登上人生巔峰...");
            executorService.shutdown();
        }
    }
    
    

CyclicBarrier

  • cyclicBarrier 重複柵欄(CountDownLatch),語法和CountDownLatch差不多,但是可重複

基本語法

  • //初始化一個cyclicBarrier 計數器為2
    CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
    //阻塞 計數器不為0的時候並且會把計數器-1
    cyclicBarrier.await();
    

與countDownlatch區別

  • 1.需要cyclicBarrier管理的程式碼塊是可以重複執行的,而countDownLatch想重複執行,必須new多個相同的countDownLatch

  • CyclicBarrier cyclicBarrier = new CyclicBarrier(2,()->{
    
        log.debug("t1 t2 end");
    });
    

    2.CyclicBarrier第二個引數的方法會在CyclicBarrier等於0後執行,起到彙總的作用

示例程式碼1

  • package BingFaBianCheng.bingFaBianCheng13.shadow.cyclic;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.*;
    import java.util.concurrent.atomic.AtomicInteger;
    @Slf4j(topic = "enjoy")
    public class CyclicBarrierTest {
        public static void main(String[] args) {
            AtomicInteger i= new AtomicInteger();
    
            CyclicBarrier cyclicBarrier = new CyclicBarrier(2,()->{
    
                log.debug("t1 t2 end");
            });
    
    
            ExecutorService service = Executors.newFixedThreadPool(2);
            for (int j = 0; j <2 ; j++) {
                service.submit(()->{
                    log.debug("start");
                    try {
                        TimeUnit.SECONDS.sleep(1);
                        log.debug("working");
                        cyclicBarrier.await();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
    
    
                });
    
                service.submit(()->{
                    log.debug("start");
                    try {
                        TimeUnit.SECONDS.sleep(3);
                        log.debug("working");
                        cyclicBarrier.await();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
            }
            service.shutdown();
        }
    }
    
    

相關文章