读写锁和两种同步方式的对比

读写锁ReentrantReadWriteLock概述

大型网站中很重要的一块内容就是数据的读写,ReentrantLock虽然具有 完全互斥排他的效果(即同一时间只有一个线程正在执行lock后面的任务),但是效率非常低。所以在JDK中提供了一种读写锁 ReentrantReadWriteLock,使用它可以加快运行效率。

读写锁表示两个锁,一个是读操作相关的锁,称为共享锁;另一个是写操作相关的锁,称为排他锁。我把这两个操作理解为三句话:

1、读和读之间不互斥,因为读操作不会有线程安全问题

2、写和写之间互斥,避免一个写操作影响另外一个写操作,引发线程安全问题

3、读和写之间互斥,避免读操作的时候写操作修改了内容,引发线程安全问题

总结起来就是,多个Thread可以同时进行读取操作,但是同一时刻只允许一个Thread进行写入操作

 

读和读共享

先证明一下第一句话"读和读之间不互斥",举一个简单的例子:

复制代码
复制代码
public class ThreadDomain48 extends ReentrantReadWriteLock
{        
    public void read() { try { readLock().lock(); System.out.println(Thread.currentThread().getName() + "获得了读锁, 时间为" + System.currentTimeMillis()); Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } finally { readLock().unlock(); } } }
复制代码
复制代码
复制代码
复制代码
public static void main(String[] args)
{
    final ThreadDomain48 td = new ThreadDomain48(); Runnable readRunnable = new Runnable() { public void run() { td.read(); } }; Thread t0 = new Thread(readRunnable); Thread t1 = new Thread(readRunnable); t0.start(); t1.start(); }
复制代码
复制代码

看一下运行结果:

Thread-0获得了读锁, 时间为1444019668424
Thread-1获得了读锁, 时间为1444019668424

尽管方法加了锁,还休眠了10秒,但是两个线程还是几乎同时执行lock()方法后面的代码,看时间就知道了。说明lock.readLock()读锁可以提高程序运行效率,允许多个线程同时执行lock()方法后面的代码

 

写和写互斥

再证明一下第二句话"写和写之间互斥",类似的证明方法:

复制代码
复制代码
public class ThreadDomain48 extends ReentrantReadWriteLock
{        
    public void write() { try { writeLock().lock(); System.out.println(Thread.currentThread().getName() + "获得了写锁, 时间为" + System.currentTimeMillis()); Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } finally { writeLock().unlock(); } } }
复制代码
复制代码
复制代码
复制代码
public static void main(String[] args)
{
    final ThreadDomain48 td = new ThreadDomain48(); Runnable readRunnable = new Runnable() { public void run() { td.write(); } }; Thread t0 = new Thread(readRunnable); Thread t1 = new Thread(readRunnable); t0.start(); t1.start(); }
复制代码
复制代码

看一下运行结果:

Thread-0获得了写锁, 时间为1444021393325
Thread-1获得了写锁, 时间为1444021403325

从时间上就可以看出来,10000ms即10s,和代码里一致,证明了读和读之间是互斥的

 

读和写互斥

最后证明一下第三句话"读和写之间互斥",证明方法无非是把上面二者结合起来而已,看一下:

复制代码
复制代码
public class ThreadDomain48 extends ReentrantReadWriteLock
{        
    public void write() { try { writeLock().lock(); System.out.println(Thread.currentThread().getName() + "获得了写锁, 时间为" + System.currentTimeMillis()); Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } finally { writeLock().unlock(); } } public void read() { try { readLock().lock(); System.out.println(Thread.currentThread().getName() + "获得了读锁, 时间为" + System.currentTimeMillis()); Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } finally { readLock().unlock(); } } }
复制代码
复制代码
复制代码
复制代码
public static void main(String[] args)
    {
        final ThreadDomain48 td = new ThreadDomain48(); Runnable readRunnable = new Runnable() { public void run() { td.read(); } }; Runnable writeRunnable = new Runnable() { public void run() { td.write(); } }; Thread t0 = new Thread(readRunnable); Thread t1 = new Thread(writeRunnable); t0.start(); t1.start(); }
复制代码
复制代码

看一下运行结果:

Thread-0获得了读锁, 时间为1444021679203
Thread-1获得了写锁, 时间为1444021689204

从时间上看,也是10000ms即10s,和代码里面是一致的,证明了读和写之间是互斥的。注意一下,"读和写互斥"和"写和读互斥"是两种不同的场景,但是证明方式和结论是一致的,所以就不证明了。

 

synchronized和ReentrantLock的对比

到现在,看到多线程中,锁定的方式有2种:synchronized和ReentrantLock。两种锁定方式各有优劣,下面简单对比一下:

1、synchronized是关键字,就和if...else...一样,是语法层面的实现,因此synchronized获取锁以及释放锁都是Java虚拟机帮助用户完成的;ReentrantLock是类层面的实现,因此锁的获取以及锁的释放都需要用户自己去操作。特别再次提醒,ReentrantLock在lock()完了,一定要手动unlock()

2、synchronized简单,简单意味着不灵活,而ReentrantLock的锁机制给用户的使用提供了极大的灵活性。这点在Hashtable和ConcurrentHashMap中体现得淋漓尽致。synchronized一锁就锁整个Hash表,而ConcurrentHashMap则利用ReentrantLock实现了锁分离,锁的知识segment而不是整个Hash表

3、synchronized是不公平锁,而ReentrantLock可以指定锁是公平的还是非公平的

4、synchronized实现等待/通知机制通知的线程是随机的,ReentrantLock实现等待/通知机制可以有选择性地通知

5、和synchronized相比,ReentrantLock提供给用户多种方法用于锁信息的获取,比如可以知道lock是否被当前线程获取、lock被同一个线程调用了几次、lock是否被任意线程获取等等

总结起来,我认为如果只需要锁定简单的方法、简单的代码块,那么考虑使用synchronized,复杂的多线程处理场景下可以考虑使用ReentrantLock。当然这只是建议性地,还是要具体场景具体分析的。

最后,查看了很多资料,JDK1.5版本只有由于对synchronized做了诸多优化,效率上synchronized和ReentrantLock应该是差不多。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
读写锁是一种用于多线程编程中的同步机制,它可以提高多线程程序的并发性能。读写锁允许多个线程同时读取共享资源,但是当有一个线程写入共享资源时,其他读取和写入操作都必须等待。在本篇文章中,我们将使用原子操作比较并交换实现对共享内存的读写锁机制。 首先,我们需要定义一个共享内存区域和两个计数器:一个用于记录当前正在读取共享内存的线程数量,另一个用于记录当前正在写入共享内存的线程数量。这些变量应该在多个线程之间共享。 ```c #include <stdatomic.h> #define MAX_THREADS 100 typedef struct { atomic_int read_count; atomic_int write_count; char shared_data[1024]; } shared_memory_t; shared_memory_t shared_memory; ``` 接下来,我们需要实现读取和写入锁。这些锁可以使用原子操作比较并交换实现。对于读取锁,我们需要使用原子操作递增读取计数器。如果当前没有线程正在写入共享内存,那么读取操作可以继续执行。否则,读取线程必须等待所有写入线程完成,然后才能开始读取共享内存。对于写入锁,我们需要使用原子操作设置写入计数器为1。如果当前没有线程正在读取或写入共享内存,那么写入操作可以继续执行。否则,写入线程必须等待所有读取和写入线程完成,然后才能开始写入共享内存。 ```c void read_lock() { while (1) { int write_count = atomic_load(&shared_memory.write_count); if (write_count == 0) { atomic_fetch_add(&shared_memory.read_count, 1); break; } } } void read_unlock() { atomic_fetch_sub(&shared_memory.read_count, 1); } void write_lock() { while (1) { int write_count = atomic_load(&shared_memory.write_count); int read_count = atomic_load(&shared_memory.read_count); if (write_count == 0 && read_count == 0 && atomic_compare_exchange_weak(&shared_memory.write_count, &write_count, 1)) { break; } } } void write_unlock() { atomic_fetch_sub(&shared_memory.write_count, 1); } ``` 最后,我们可以使用这些锁来保护共享内存。例如,下面的代码展示了如何在多个线程之间读取和写入共享内存。 ```c void* reader(void* arg) { read_lock(); printf("Read: %s\n", shared_memory.shared_data); read_unlock(); } void* writer(void* arg) { write_lock(); sprintf(shared_memory.shared_data, "Hello, world!"); write_unlock(); } int main() { pthread_t threads[MAX_THREADS]; int i; for (i = 0; i < 10; i++) { pthread_create(&threads[i], NULL, reader, NULL); } for (i = 10; i < 20; i++) { pthread_create(&threads[i], NULL, writer, NULL); } for (i = 0; i < 20; i++) { pthread_join(threads[i], NULL); } return 0; } ``` 在这个例子中,我们创建了10个读取线程和10个写入线程,并且使用读取锁和写入锁来保护共享内存。这样,我们就实现了对共享内存的读写锁机制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值