Java中的读写锁

发布时间:2024年01月18日

一、ReentrantReadWriteLock

两个线程同时读取,不会互斥:

@Slf4j
public class ReadWriteLockTest {
    public static void main(String[] args) throws InterruptedException {
        DataContainer dataContainer = new DataContainer();
        new Thread(dataContainer::read, "t1").start();
        //Thread.sleep(1000);
        new Thread(dataContainer::read, "t2").start();
    }
}

@Slf4j
class DataContainer {
    private Object data;
    private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
    private ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();

    public Object read() {
        readLock.lock();
        log.debug("获取读锁");
        try {
            log.debug("读取");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return data;
        } finally {
            readLock.unlock();
            log.debug("释放读锁");
        }
    }

    public void write() {
        writeLock.lock();
        log.debug("获取写锁");
        try {
            log.debug("写入");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } finally {
            writeLock.unlock();
            log.debug("释放写锁");
        }
    }
}

13:52:32.177 [t2] DEBUG juc.readwrite.DataContainer - 获取读锁
13:52:32.177 [t1] DEBUG juc.readwrite.DataContainer - 获取读锁
13:52:32.180 [t1] DEBUG juc.readwrite.DataContainer - 读取
13:52:32.180 [t2] DEBUG juc.readwrite.DataContainer - 读取
13:52:33.181 [t1] DEBUG juc.readwrite.DataContainer - 释放读锁
13:52:33.181 [t2] DEBUG juc.readwrite.DataContainer - 释放读锁

一个线程读取,一个线程写入,会互斥:

@Slf4j
public class ReadWriteLockTest {
    public static void main(String[] args) throws InterruptedException {
        DataContainer dataContainer = new DataContainer();
        new Thread(dataContainer::read, "t1").start();
        Thread.sleep(1000);
        new Thread(dataContainer::write, "t2").start();
    }
}

@Slf4j
class DataContainer {
    private Object data;
    private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
    private ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();

    public Object read() {
        readLock.lock();
        log.debug("获取读锁");
        try {
            log.debug("读取");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return data;
        } finally {
            readLock.unlock();
            log.debug("释放读锁");
        }
    }

    public void write() {
        writeLock.lock();
        log.debug("获取写锁");
        try {
            log.debug("写入");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } finally {
            writeLock.unlock();
            log.debug("释放写锁");
        }
    }
}
13:53:26.922 [t1] DEBUG juc.readwrite.DataContainer - 获取读锁
13:53:26.924 [t1] DEBUG juc.readwrite.DataContainer - 读取
13:53:27.928 [t1] DEBUG juc.readwrite.DataContainer - 释放读锁
13:53:27.929 [t2] DEBUG juc.readwrite.DataContainer - 获取写锁
13:53:27.929 [t2] DEBUG juc.readwrite.DataContainer - 写入
13:53:28.931 [t2] DEBUG juc.readwrite.DataContainer - 释放写锁

两个线程读同时写入,会互斥:

@Slf4j
public class ReadWriteLockTest {
    public static void main(String[] args) throws InterruptedException {
        DataContainer dataContainer = new DataContainer();
        new Thread(dataContainer::write, "t1").start();
        Thread.sleep(1000);
        new Thread(dataContainer::write, "t2").start();
    }
}

@Slf4j
class DataContainer {
    private Object data;
    private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
    private ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();

    public Object read() {
        readLock.lock();
        log.debug("获取读锁");
        try {
            log.debug("读取");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return data;
        } finally {
            readLock.unlock();
            log.debug("释放读锁");
        }
    }

    public void write() {
        writeLock.lock();
        log.debug("获取写锁");
        try {
            log.debug("写入");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } finally {
            writeLock.unlock();
            log.debug("释放写锁");
        }
    }
}
13:55:19.834 [t1] DEBUG juc.readwrite.DataContainer - 获取写锁
13:55:19.837 [t1] DEBUG juc.readwrite.DataContainer - 写入
13:55:20.840 [t1] DEBUG juc.readwrite.DataContainer - 释放写锁
13:55:20.849 [t2] DEBUG juc.readwrite.DataContainer - 获取写锁
13:55:20.849 [t2] DEBUG juc.readwrite.DataContainer - 写入
13:55:21.850 [t2] DEBUG juc.readwrite.DataContainer - 释放写锁
1.读锁不支持条件变量
2.锁升级不支持重入

持有读锁的情况下去获取写锁,会导致写锁永久等待:

r.lock();
try{
  //...
  w.lock();
  try{
    //...
  } finally{
    w.unlock();
 }
} finally{
  r.unlock();
}
3.锁降级支持重入

持有写锁的情况下去获取读锁,支持。

class CachedData {
    Object data;
    // 是否有效,如果失效,需要重新计算 data
    volatile boolean cacheValid;
    final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

    void processCachedData() {
        rwl.readLock().lock();
        if (!cacheValid) {
            //获取写锁前必须释放读锁
            rwl.readLock().unlock();
            rwl.writeLock().lock();
            try {
                // 判断是否有其它线程已经获取了写锁、更新了缓存,避免重复更新
                if (!cacheValid) {
                    data = ...
                    cacheValid = true;
                }
                // 降级为读锁,释放写锁,这样能够让其它线程读取缓存
                rwl.readLock().lock();
            } finally {
                rwl.writeLock().unlock();
            }
        }
        // 自己用完数据,释放读锁
        try {
            use(data);
        } finally {
            rwl.readLock().unlock();
        }
    }
}

二、缓存更新策略

1.先清缓存,再更新数据库

先清缓存.png

2.先更新数据库,再清缓存

image.png

使用读写锁解决:

public class TestGenericDao {
    public static void main(String[] args) {
        GenericDao dao = new GenericDaoCached();
        System.out.println("============> 查询");
        String sql = "select * from emp where empno = ?";
        int empno = 7369;
        Emp emp = dao.queryOne(Emp.class, sql, empno);
        System.out.println(emp);
        emp = dao.queryOne(Emp.class, sql, empno);
        System.out.println(emp);
        emp = dao.queryOne(Emp.class, sql, empno);
        System.out.println(emp);

        System.out.println("============> 更新");
        dao.update("update emp set sal = ? where empno = ?", 800, empno);
        emp = dao.queryOne(Emp.class, sql, empno);
        System.out.println(emp);
    }
}

class GenericDaoCached extends GenericDao {
    private GenericDao dao = new GenericDao();
    private Map<SqlPair, Object> map = new HashMap<>();
    private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();

    @Override
    public <T> List<T> queryList(Class<T> beanClass, String sql, Object... args) {
        return dao.queryList(beanClass, sql, args);
    }

    @Override
    public <T> T queryOne(Class<T> beanClass, String sql, Object... args) {
        // 先从缓存中找,找到直接返回
        SqlPair key = new SqlPair(sql, args);;
        rw.readLock().lock();
        try {
            T value = (T) map.get(key);
            if(value != null) {
                return value;
            }
        } finally {
            rw.readLock().unlock();
        }
        rw.writeLock().lock();
        try {
            // 多个线程
            T value = (T) map.get(key);
            if(value == null) {
                // 缓存中没有,查询数据库
                value = dao.queryOne(beanClass, sql, args);
                map.put(key, value);
            }
            return value;
        } finally {
            rw.writeLock().unlock();
        }
    }

    @Override
    public int update(String sql, Object... args) {
        rw.writeLock().lock();
        try {
            // 先更新库
            int update = dao.update(sql, args);
            // 清空缓存
            map.clear();
            return update;
        } finally {
            rw.writeLock().unlock();
        }
    }

    class SqlPair {
        private String sql;
        private Object[] args;

        public SqlPair(String sql, Object[] args) {
            this.sql = sql;
            this.args = args;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            SqlPair sqlPair = (SqlPair) o;
            return Objects.equals(sql, sqlPair.sql) &&
                    Arrays.equals(args, sqlPair.args);
        }

        @Override
        public int hashCode() {
            int result = Objects.hash(sql);
            result = 31 * result + Arrays.hashCode(args);
            return result;
        }
    }
}

三、StampedLock

JDK 8 加入,是为了进一步优化读性能,它的特点是在使用读锁、写锁时都必须配合【戳】使用:
加解读锁:

long stamp = lock.readLock();
lock.unlockRead(stamp);

加解写锁:

long stamp = lock.writeLock();
lock.unlockWrite(stamp);

乐观读
乐观读,StampedLock 支持 tryOptimisticRead() 方法(乐观读),读取完毕后需要做一次 戳校验, 如果校验通过,表示这期间确实没有写操作,数据可以安全使用,如果校验没通过,需要重新获取读锁,保证数据安全。

long stamp = lock.tryOptimisticRead();
// 验戳
if(!lock.validate(stamp)){
// 锁升级
}

读读,使用乐观读:

package juc.readwrite;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.StampedLock;


@Slf4j
public class StampedLockTest {
    public static void main(String[] args) throws InterruptedException {
        DataContainerStamped dataContainer = new DataContainerStamped(1);
        new Thread(() -> dataContainer.read(1000), "t1").start();
        Thread.sleep(500);
        new Thread(() -> dataContainer.read(0), "t2").start();
    }
}

@Slf4j
class DataContainerStamped {
    private int data;
    private final StampedLock lock = new StampedLock();

    public DataContainerStamped(int data) {
        this.data = data;
    }

    public int read(int readTime) {
        long stamp = lock.tryOptimisticRead();
        log.debug("optimistic read locking...{}", stamp);
        try {
            Thread.sleep(readTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (lock.validate(stamp)) {
            log.debug("read finish...{}, data:{}", stamp, data);
            return data;
        }
        // 锁升级 - 读锁
        log.debug("updating to read lock... {}", stamp);
        try {
            stamp = lock.readLock();
            log.debug("read lock {}", stamp);
            try {
                Thread.sleep(readTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("read finish...{}, data:{}", stamp, data);
            return data;
        } finally {
            log.debug("read unlock {}", stamp);
            lock.unlockRead(stamp);
        }
    }

    public void write(int newData) {
        long stamp = lock.writeLock();
        log.debug("write lock {}", stamp);
        try {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.data = newData;
        } finally {
            log.debug("write unlock {}", stamp);
            lock.unlockWrite(stamp);
        }
    }
}
16:32:53.802 [t1] DEBUG juc.readwrite.DataContainerStamped - optimistic read locking...256
16:32:54.299 [t2] DEBUG juc.readwrite.DataContainerStamped - optimistic read locking...256
16:32:54.300 [t2] DEBUG juc.readwrite.DataContainerStamped - read finish...256, data:1
16:32:54.814 [t1] DEBUG juc.readwrite.DataContainerStamped - read finish...256, data:1

读写,乐观读升级为读锁:

@Slf4j
public class StampedLockTest {
    public static void main(String[] args) throws InterruptedException {
        DataContainerStamped dataContainer = new DataContainerStamped(1);
        new Thread(() -> dataContainer.read(1000), "t1").start();
        Thread.sleep(500);
        new Thread(() -> dataContainer.write(1000), "t2").start();
    }
}
16:35:01.050 [t1] DEBUG juc.readwrite.DataContainerStamped - optimistic read locking...256
16:35:01.548 [t2] DEBUG juc.readwrite.DataContainerStamped - write lock 384
16:35:02.057 [t1] DEBUG juc.readwrite.DataContainerStamped - updating to read lock... 256
16:35:03.549 [t2] DEBUG juc.readwrite.DataContainerStamped - write unlock 384
16:35:03.549 [t1] DEBUG juc.readwrite.DataContainerStamped - read lock 513
16:35:04.551 [t1] DEBUG juc.readwrite.DataContainerStamped - read finish...513, data:1000
16:35:04.551 [t1] DEBUG juc.readwrite.DataContainerStamped - read unlock 513

不支持条件变量,不支持重入

文章来源:https://blog.csdn.net/CSDN_GIA/article/details/135666039
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。