ReadWriteLock是什么?

目录

1、ReadWriteLock是什么?

2、使用举例:

3、总结

4、拓展:ReadWriteLock读写锁的使用 

4.1、ReetrantReadWriteLock获取锁顺序:

4.2、ReetrantReadWriteLock可重入

4.3、ReetrantReadWriteLock锁降级

4.3.1、Test Code 1

4.3.2、Test Code 2

5、ReetrantReadWriteLock对比使用

6、ReetrantReadWriteLock读写锁互斥关系

7、 ReetrantReadWriteLock写锁关系

8、总结


1、ReadWriteLock是什么?

        ReadWriteLock是一个接口,只有一个实现类ReentrantReadWriteLock 读写锁:读的时候可以多线程同时读写的时候只能有一个线程单独写,读的时候不允许写,读锁readLock共享锁, 写锁writeLock独占锁

在这里插入图片描述


2、使用举例:

public class ReadWriteLockTest {
    public static void main(String[] args) {
        Data data = new Data();
        // 多线程同时写
        for (int i = 1;i <= 5;i++){
            new Thread(()->{
                data.write();
            },String.valueOf(i)).start();
        }
        // 多线程同时读
        for (int i = 1;i <= 5;i++){
            new Thread(()->{
                data.read();
            },String.valueOf(i)).start();
        }
    }
}
// 资源类
class Data {
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); //创建读写锁
    // 写:写的时候只能有一个线程单独写
    public void write(){
        readWriteLock.writeLock().lock(); // 写锁加锁
        try {
            System.out.println(Thread.currentThread().getName()+"正在写入");
            TimeUnit.SECONDS.sleep(1); // 延时1秒
            System.out.println(Thread.currentThread().getName()+"写入结束");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock(); // 写锁解锁
        }
    }
    // 读:读的时候可以多线程同时读
    public void read(){
       readWriteLock.readLock().lock(); // 读锁加锁
       try {
           System.out.println(Thread.currentThread().getName()+"正在读取");
           TimeUnit.SECONDS.sleep(1); // 延时1秒
           System.out.println(Thread.currentThread().getName()+"读取结束");
       }catch (InterruptedException e) {
           e.printStackTrace();
       }finally {
           readWriteLock.readLock().unlock(); // 读锁解锁
       }
    }
}

 结果:

2正在写入
2写入结束
1正在写入
1写入结束
4正在写入
4写入结束
3正在写入
3写入结束
5正在写入
5写入结束
1正在读取
2正在读取
3正在读取
4正在读取
5正在读取
1读取结束
2读取结束
5读取结束
4读取结束
3读取结束

3、总结

       首先明确一下,不是说ReentrantLock不好,只是ReentrantLock某些时候有局限。如果使用ReentrantLock,可能本身是为了防止线程A在写数据、线程B在读数据造成的数据不一致,但这样,如果线程C在读数据、线程D也在读数据,读数据是不会改变数据的,没有必要加锁,但是还是加锁了,降低了程序的性能。

        因为这个,才诞生了读写锁ReadWriteLock。ReadWriteLock是一个读写锁接口,ReentrantReadWriteLock是ReadWriteLock接口的一个具体实现,实现了读写的分离,读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。


4、拓展:ReadWriteLock读写锁的使用 

Java并发包中ReadWriteLock是一个接口,主要有两个方法,如下:

public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading
     */
    Lock readLock();
 
    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing
     */
    Lock writeLock();
}

        ReadWriteLock管理一组锁,一个是只读的锁,一个是写锁。
        Java并发库中ReetrantReadWriteLock实现了ReadWriteLock接口并添加了可重入的特性。在具体讲解ReetrantReadWriteLock的使用方法前,我们有必要先对其几个特性进行一些深入学习了解。

4.1、ReetrantReadWriteLock获取锁顺序:

  • 非公平模式(默认)
    当以非公平初始化时,读锁和写锁的获取的顺序是不确定的。非公平锁主张竞争获取,可能会延缓一个或多个读或写线程,但是会比公平锁有更高的吞吐量。
  • 公平模式
    当以公平模式初始化时,线程将会以队列的顺序获取锁。当当前线程释放锁后,等待时间最长的写锁线程就会被分配写锁;或者有一组读线程组等待时间比写线程长,那么这组读线程组将会被分配读锁。

4.2、ReetrantReadWriteLock可重入

         什么是可重入锁,不可重入锁呢?"重入"字面意思已经很明显了,就是可以重新进入。可重入锁,就是说一个线程在获取某个锁后,还可以继续获取该锁,即允许一个线程多次获取同一个锁。比如synchronized内置锁就是可重入的,如果A类有2个synchornized方法method1和method2,那么method1调用method2是允许的。显然重入锁给编程带来了极大的方便。假如内置锁不是可重入的,那么导致的问题是:1个类的synchornized方法不能调用本类其他synchornized方法,也不能调用父类中的synchornized方法。与内置锁对应,JDK提供的显示锁ReentrantLock也是可以重入的,这里通过一个例子着重说下可重入锁的释放需要的事儿。

package test;
 
import java.util.concurrent.locks.ReentrantReadWriteLock;
 
public class Test1 {
 
    public static void main(String[] args) throws InterruptedException {
        final ReentrantReadWriteLock  lock = new ReentrantReadWriteLock ();
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.writeLock().lock();
                System.out.println("Thread real execute");
                lock.writeLock().unlock();
            }
        });
 
        lock.writeLock().lock();
        lock.writeLock().lock();
        t.start();
        Thread.sleep(200);
        
        System.out.println("realse one once");
        lock.writeLock().unlock();
    }
 
}

运行结果: 

         从运行结果中,可以看到,程序并未执行线程的run方法,由此我们可知,上面的代码会出现死锁,因为主线程2次获取了锁,但是却只释放1次锁,导致线程t永远也不能获取锁。一个线程获取多少次锁,就必须释放多少次锁。这对于内置锁也是适用的,每一次进入和离开synchornized方法(代码块),就是一次完整的锁获取和释放。

再次添加一次unlock之后的运行结果.png:

4.3、ReetrantReadWriteLock锁降级

        要实现一个读写锁,需要考虑很多细节,其中之一就是锁升级和锁降级的问题。什么是升级和降级呢?ReadWriteLock的javadoc有一段话:

Can the write lock be downgraded to a read lock without allowing an intervening writer? Can a read lock be upgraded to a write lock, in preference to other waiting readers or writers?

翻译过来的结果是:在不允许中间写入的情况下,写入锁可以降级为读锁吗?读锁是否可以升级为写锁,优先于其他等待的读取或写入操作?简言之就是说,锁降级:从写锁变成读锁;锁升级:从读锁变成写锁,ReadWriteLock是否支持呢?让我们带着疑问,进行一些Demo 测试代码验证。

4.3.1、Test Code 1

/**
 *Test Code 1
 **/
package test;
 
import java.util.concurrent.locks.ReentrantReadWriteLock;
 
public class Test1 {
 
    public static void main(String[] args) {
        ReentrantReadWriteLock rtLock = new ReentrantReadWriteLock();
        rtLock.readLock().lock();
        System.out.println("get readLock.");
        rtLock.writeLock().lock();
        System.out.println("blocking");
    }
}

        结论:上面的测试代码会产生死锁,因为同一个线程中,在没有释放读锁的情况下,就去申请写锁,这属于锁升级,ReentrantReadWriteLock是不支持的。 

4.3.2、Test Code 2

/**
 *Test Code 2
 **/
package test;
 
import java.util.concurrent.locks.ReentrantReadWriteLock;
 
public class Test2 {
 
    public static void main(String[] args) {
        ReentrantReadWriteLock rtLock = new ReentrantReadWriteLock();  
        rtLock.writeLock().lock();  
        System.out.println("writeLock");  
          
        rtLock.readLock().lock();  
        System.out.println("get read lock");  
    }
}

        结论:ReentrantReadWriteLock支持锁降级,上面代码不会产生死锁。这段代码虽然不会导致死锁,但没有正确的释放锁。从写锁降级成读锁,并不会自动释放当前线程获取的写锁,仍然需要显示的释放,否则别的线程永远也获取不到写锁。 


5、ReetrantReadWriteLock对比使用

        在使用ReetrantReadWriteLock实现锁机制前,我们先看一下,多线程同时读取文件时,用synchronized实现的效果 

package test;
 
/**
 * 
 * synchronized实现
 * @author itbird
 *
 */
public class ReadAndWriteLockTest {
 
    public synchronized static void get(Thread thread) {
        System.out.println("start time:" + System.currentTimeMillis());
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(thread.getName() + ":正在进行读操作……");
        }
        System.out.println(thread.getName() + ":读操作完毕!");
        System.out.println("end time:" + System.currentTimeMillis());
    }
 
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                get(Thread.currentThread());
            }
        }).start();
 
        new Thread(new Runnable() {
            @Override
            public void run() {
                get(Thread.currentThread());
            }
        }).start();
    }
 
}

让我们看一下运行结果:

从运行结果可以看出,两个线程的读操作是顺序执行的,整个过程大概耗时200ms。


ReetrantReadWriteLock实现: 

package test;
 
import java.util.concurrent.locks.ReentrantReadWriteLock;
 
/**
 * 
 * ReetrantReadWriteLock实现
 * @author itbird
 *
 */
public class ReadAndWriteLockTest {
 
    public static void get(Thread thread) {
        ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        lock.readLock().lock();
        System.out.println("start time:" + System.currentTimeMillis());
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(thread.getName() + ":正在进行读操作……");
        }
        System.out.println(thread.getName() + ":读操作完毕!");
        System.out.println("end time:" + System.currentTimeMillis());
        lock.readLock().unlock();
    }
 
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                get(Thread.currentThread());
            }
        }).start();
 
        new Thread(new Runnable() {
            @Override
            public void run() {
                get(Thread.currentThread());
            }
        }).start();
    }
 
}

让我们看一下运行结果:

        从运行结果可以看出,两个线程的读操作是同时执行的,整个过程大概耗时100ms。
通过两次实验的对比,我们可以看出来,ReetrantReadWriteLock的效率明显高于Synchronized关键字。 


6、ReetrantReadWriteLock读写锁互斥关系

        通过上面的测试代码,我们也可以延伸得出一个结论,ReetrantReadWriteLock读锁使用共享模式,即:同时可以有多个线程并发地读数据。但是另一个问题来了,写锁之间是共享模式还是互斥模式?读写锁之间是共享模式还是互斥模式呢?下面让我们通过Demo进行一一验证吧。

ReetrantReadWriteLock读写锁关系:

package test;
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantReadWriteLock;
 
/**
 * 
 * ReetrantReadWriteLock实现
 * @author itbird
 *
 */
public class ReadAndWriteLockTest {
 
    public static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
 
    public static void main(String[] args) {
        //同时读、写
        ExecutorService service = Executors.newCachedThreadPool();
        service.execute(new Runnable() {
            @Override
            public void run() {
                readFile(Thread.currentThread());
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                writeFile(Thread.currentThread());
            }
        });
    }
 
    // 读操作
    public static void readFile(Thread thread) {
        lock.readLock().lock();
        boolean readLock = lock.isWriteLocked();
        if (!readLock) {
            System.out.println("当前为读锁!");
        }
        try {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(thread.getName() + ":正在进行读操作……");
            }
            System.out.println(thread.getName() + ":读操作完毕!");
        } finally {
            System.out.println("释放读锁!");
            lock.readLock().unlock();
        }
    }
 
    // 写操作
    public static void writeFile(Thread thread) {
        lock.writeLock().lock();
        boolean writeLock = lock.isWriteLocked();
        if (writeLock) {
            System.out.println("当前为写锁!");
        }
        try {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(thread.getName() + ":正在进行写操作……");
            }
            System.out.println(thread.getName() + ":写操作完毕!");
        } finally {
            System.out.println("释放写锁!");
            lock.writeLock().unlock();
        }
    }
}
 

 运行结果:

结论:读写锁的实现必须确保写操作对读操作的内存影响。换句话说,一个获得了读锁的线程必须能看到前一个释放的写锁所更新的内容,读写锁之间为互斥。 

7、 ReetrantReadWriteLock写锁关系

package test;
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantReadWriteLock;
 
/**
 * 
 * ReetrantReadWriteLock实现
 * @author itbird
 *
 */
public class ReadAndWriteLockTest {
 
    public static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
 
    public static void main(String[] args) {
        //同时写
        ExecutorService service = Executors.newCachedThreadPool();
        service.execute(new Runnable() {
            @Override
            public void run() {
                writeFile(Thread.currentThread());
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                writeFile(Thread.currentThread());
            }
        });
    }
 
    // 读操作
    public static void readFile(Thread thread) {
        lock.readLock().lock();
        boolean readLock = lock.isWriteLocked();
        if (!readLock) {
            System.out.println("当前为读锁!");
        }
        try {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(thread.getName() + ":正在进行读操作……");
            }
            System.out.println(thread.getName() + ":读操作完毕!");
        } finally {
            System.out.println("释放读锁!");
            lock.readLock().unlock();
        }
    }
 
    // 写操作
    public static void writeFile(Thread thread) {
        lock.writeLock().lock();
        boolean writeLock = lock.isWriteLocked();
        if (writeLock) {
            System.out.println("当前为写锁!");
        }
        try {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(thread.getName() + ":正在进行写操作……");
            }
            System.out.println(thread.getName() + ":写操作完毕!");
        } finally {
            System.out.println("释放写锁!");
            lock.writeLock().unlock();
        }
    }
}
 

 运行结果:

8、总结

  1. Java并发库中ReetrantReadWriteLock实现了ReadWriteLock接口并添加了可重入的特性
  2. ReetrantReadWriteLock读写锁的效率明显高于synchronized关键字
  3. ReetrantReadWriteLock读写锁的实现中,读锁使用共享模式;写锁使用独占模式,换句话说,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的
  4. ReetrantReadWriteLock读写锁的实现中,需要注意的,当有读锁时,写锁就不能获得;而当有写锁时,除了获得写锁的这个线程可以获得读锁外,其他线程不能获得读锁 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值