天天看點

JUC之ReadWriteLock-多線程與高并發

ReadWriteLock讀寫鎖分為讀鎖和寫鎖,讀鎖是共享鎖即多個讀線程可以同時讀,但是如果有線程在進行寫操作,則所有讀線程都阻塞等待了。讀寫鎖相對于synchronized來說,是進一步優化了,synchronized保證了兩個線程不能同時執行一段代碼,而讀寫鎖則具體分析了一下,如果都是讀操作的線程,則不用進行阻塞。

寫鎖是排他鎖,隻要有線程在讀,則不能進行寫操作;有線程在寫,當然也不能再讓其他寫線程獲得寫鎖。

讀寫鎖有兩種實作,一種是ReentrantReadWriteLock類,一種是StampedLock類。下面是應用的示例代碼。

1、 ReentrantReadWriteLock

通過ReentrantReadWriteLock擷取讀鎖和寫鎖對象,讀操作的代碼加讀鎖,寫操作的代碼加寫鎖。lock()和unlock()。

package basic.aqs.readWriteLock;

import java.util.Random;
import java.util.concurrent.atomic.LongAdder;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

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 writeR = ()->write(lock, new Random().nextInt());

        Runnable readR = ()-> read(readLock);
        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();


    }
}
           

2、StampedLock

StampedLock分為三種鎖,讀鎖、寫鎖、樂觀讀鎖。

(1)進入悲觀讀鎖前先看下有沒有進入寫模式(說白了就是有沒有已經擷取了悲觀寫鎖)

(2)如果其他線程已經擷取了悲觀寫鎖,那麼就隻能老老實實的擷取悲觀讀鎖(這種情況相當于退化成了讀寫鎖)

(3)如果其他線程沒有擷取悲觀寫鎖,那麼就不用擷取悲觀讀鎖了,減少了一次擷取悲觀讀鎖的消耗和避免了因為讀鎖導緻寫鎖阻塞的問題,直接傳回讀的資料即可(必須再tryOptimisticRead和validate之間擷取好資料,否則資料可能會不一緻了,試想如果過了validate再擷取資料,這時資料可能被修改并且讀操作也沒有任何保護措施)

package basic.aqs.readWriteLock;

import java.util.concurrent.locks.StampedLock;

public class TestStampedLock {

    class Point {
        private double x, y;
        private final StampedLock sl = new StampedLock();

        void move(double deltaX, double deltaY) { // an exclusively locked method
            long stamp = sl.writeLock();
            try {
                x += deltaX;
                y += deltaY;
            } finally {
                sl.unlockWrite(stamp);
            }
        }

        //下面看看樂觀讀鎖案例
        double distanceFromOrigin() { // A read-only method
            long stamp = sl.tryOptimisticRead(); //獲得一個樂觀讀鎖
            double currentX = x, currentY = y;  //将兩個字段讀入本地局部變量
            if (!sl.validate(stamp)) { //檢查發出樂觀讀鎖後同時是否有其他寫鎖發生?
                stamp = sl.readLock();  //如果沒有,我們再次獲得一個讀悲觀鎖
                try {
                    currentX = x; // 将兩個字段讀入本地局部變量
                    currentY = y; // 将兩個字段讀入本地局部變量
                } finally {
                    sl.unlockRead(stamp);
                }
            }
            return Math.sqrt(currentX * currentX + currentY * currentY);
        }

        //下面是悲觀讀鎖案例
        void moveIfAtOrigin(double newX, double newY) { // upgrade
            // Could instead start with optimistic, not read mode
            long stamp = sl.readLock();
            try {
                while (x == 0.0 && y == 0.0) { //循環,檢查目前狀态是否符合
                    long ws = sl.tryConvertToWriteLock(stamp); //将讀鎖轉為寫鎖
                    if (ws != 0L) { //這是确認轉為寫鎖是否成功
                        stamp = ws; //如果成功 替換票據
                        x = newX; //進行狀态改變
                        y = newY;  //進行狀态改變
                        break;
                    } else { //如果不能成功轉換為寫鎖
                        sl.unlockRead(stamp);  //我們顯式釋放讀鎖
                        stamp = sl.writeLock();  //顯式直接進行寫鎖 然後再通過循環再試
                    }
                }
            } finally {
                sl.unlock(stamp); //釋放讀鎖或寫鎖
            }
        }
    }
}