Java多线程 -- 互斥锁/共享锁/读写锁 快速入门
什么是互斥锁?
在访问共享资源之前对进行加锁操作,在访问完成之后进行解锁操作。 加锁后,任何其他试图再次加锁的线程会被阻塞,直到当前进程解锁。
如果解锁时有一个以上的线程阻塞,那么所有该锁上的线程都被编程就绪状态, 第一个变为就绪状态的线程又执行加锁操作,那么其他的线程又会进入等待。 在这种方式下,只有一个线程能够访问被互斥锁保护的资源。
什么是共享锁?
互斥锁要求只能有一个线程访问被保护的资源,共享锁从字面来看也即是允许多个线程共同访问资源。
什么是读写锁?
读写锁既是互斥锁,又是共享锁,read模式是共享,write是互斥(排它锁)的。
读写锁有三种状态:读加锁状态、写加锁状态和不加锁状态
一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。
用ReentrantLock手动实现一个简单的读写锁。
MyReadWriteLock.java
/** * Created by Fant.J. */ public class MyReadWriteLock { private Map<String,Object> map = new HashMap<>(); private ReadWriteLock rwl = new ReentrantReadWriteLock(); private Lock r = rwl.readLock(); private Lock w = rwl.writeLock(); public Object get(String key){ try { r.lock(); System.out.println(Thread.currentThread().getName()+"read 操作执行"); Thread.sleep(500); return map.get(key); } catch (InterruptedException e) { e.printStackTrace(); return null; } finally { r.unlock(); System.out.println(Thread.currentThread().getName()+"read 操作结束"); } } public void put(String key,Object value){ try { w.lock(); System.out.println(Thread.currentThread().getName()+"write 操作执行"); Thread.sleep(500); map.put(key,value); } catch (InterruptedException e) { e.printStackTrace(); } finally { w.unlock(); System.out.println(Thread.currentThread().getName()+"write 操作结束"); } } }
测试读读共享(不互斥)
/** * Created by Fant.J. */ public class Test { public static void main(String[] args) { MyReadWriteLock myReadWriteLock = new MyReadWriteLock(); myReadWriteLock.put("a","fantj_a"); //读读不互斥(共享) //读写互斥 new Thread(new Runnable() { @Override public void run() { System.out.println(myReadWriteLock.get("a")); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(myReadWriteLock.get("a")); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(myReadWriteLock.get("a")); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(myReadWriteLock.get("a")); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(myReadWriteLock.get("a")); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(myReadWriteLock.get("a")); } }).start(); } } mainwrite 操作执行 mainwrite 操作结束 Thread-1read 操作执行 Thread-2read 操作执行 Thread-0read 操作执行 Thread-3read 操作执行 Thread-4read 操作执行 Thread-5read 操作执行 Thread-1read 操作结束 Thread-0read 操作结束 Thread-2read 操作结束 Thread-3read 操作结束 fantj_a fantj_a fantj_a fantj_a Thread-4read 操作结束 fantj_a Thread-5read 操作结束 fantj_a
可以看出,中间有很多read操作是并发进行的。
那么我们再看下写写是否有互斥性:
/** * 测试 写-写 模式 是互斥的 * Created by Fant.J. */ public class TestWriteWrite { public static void main(String[] args) { MyReadWriteLock myReadWriteLock = new MyReadWriteLock(); new Thread(new Runnable() { @Override public void run() { myReadWriteLock.put("b","fantj_b"); } }).start(); new Thread(new Runnable() { @Override public void run() { myReadWriteLock.put("b","fantj_b"); } }).start(); new Thread(new Runnable() { @Override public void run() { myReadWriteLock.put("b","fantj_b"); } }).start(); new Thread(new Runnable() { @Override public void run() { myReadWriteLock.put("b","fantj_b"); } }).start(); new Thread(new Runnable() { @Override public void run() { myReadWriteLock.put("b","fantj_b"); } }).start(); } } Thread-0write 操作执行 Thread-1write 操作执行 Thread-0write 操作结束 Thread-1write 操作结束 Thread-2write 操作执行 Thread-2write 操作结束 Thread-3write 操作执行 Thread-3write 操作结束 Thread-4write 操作执行 Thread-4write 操作结束
控制台能明显感觉到线程休息的时间。所以它的写-写操作肯定是互斥的。
最后再看看,写-读 操作是否互斥。
写-读互斥 测试
/** * 测试 写-读模式 互斥 * Created by Fant.J. */ public class TestWriteRead { public static void main(String[] args) { MyReadWriteLock myReadWriteLock = new MyReadWriteLock(); //读读不互斥(共享) //读写互斥 new Thread(new Runnable() { @Override public void run() { myReadWriteLock.put("a","fantj_a"); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(myReadWriteLock.get("a")); } }).start(); } } Thread-0write 操作执行 Thread-1read 操作执行 Thread-0write 操作结束 Thread-1read 操作结束 fantj_a
控制台可以看到write操作执行后线程被阻塞。直到写释放了锁。
问题分析
问题1:仔细想了想,如果有一种场景,就是用户一直再读,写获取不到锁,那么不就造成脏读吗?
上一章我介绍了公平锁/非公平锁,资源的抢占不就是非公平锁造成的吗,那我们用公平锁把它包装下不就能避免了吗,我做了个简单的实现:(不知道公平锁的可以翻我上章教程)
/** * 测试 读写锁 的公平锁 实现 * Created by Fant.J. */ public class TestReadWriteRead { public static void main(String[] args) { ReentrantLock fairLock = new ReentrantLock(true); MyReadWriteLock myReadWriteLock = new MyReadWriteLock(); myReadWriteLock.put("a","fantj_a"); new Thread(new Runnable() { @Override public void run() { fairLock.lock(); System.out.println(myReadWriteLock.get("a")); System.out.println("fair队列长度"+fairLock.getQueueLength()); fairLock.unlock(); } }).start(); new Thread(new Runnable() { @Override public void run() { fairLock.lock(); System.out.println(myReadWriteLock.get("a")); System.out.println("fair队列长度"+fairLock.getQueueLength()); fairLock.unlock(); } }).start(); new Thread(new Runnable() { @Override public void run() { fairLock.lock(); System.out.println(myReadWriteLock.get("a")); System.out.println("fair队列长度"+fairLock.getQueueLength()); fairLock.unlock(); } }).start(); new Thread(new Runnable() { @Override public void run() { fairLock.lock(); myReadWriteLock.put("a","fantj_a_update"); System.out.println("fair队列长度"+fairLock.getQueueLength()); fairLock.unlock(); } }).start(); new Thread(new Runnable() { @Override public void run() { fairLock.lock(); System.out.println(myReadWriteLock.get("a")); System.out.println("fair队列长度"+fairLock.getQueueLength()); fairLock.unlock(); } }).start(); } } mainwrite 操作执行 mainwrite 操作结束 Thread-0read 操作执行 Thread-0read 操作结束 fantj_a fair队列长度4 Thread-1read 操作执行 Thread-1read 操作结束 fantj_a fair队列长度3 Thread-2read 操作执行 Thread-2read 操作结束 fantj_a fair队列长度2 Thread-3write 操作执行 Thread-3write 操作结束 fair队列长度1 Thread-4read 操作执行 Thread-4read 操作结束 fantj_a_update fair队列长度0
如果谁有更好的实现方式(或者java有现成的实现工具类/包),可在评论区留言,我在百度上没有找到读写锁的公平锁实现~~谢谢!

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
C++程序设计基础(5)sizeof的使用
1.知识点 (1)sizeof是一个单目运算发,而不是一个函数,其用于获取操作数所占内存空间的字节数。 (2)sizeof的操作数可以使类型名,也可以是表达式,如果是类型名则直接获得该类型所占字节数,如果是表达式,则先分析表达式结果的类型,再根据类型确定所占字节数,并不对表达式进行实际计算。 1 int a = 1; 2 double b = 1.5; 3 sizeof(int);//结果为4 4 sizeof(a); //结果为4 5 sizeof(b); //结果为8 (3)sizeof很少单独使用,而是和内存分配或者计算法数组长度等需求进行配合使用。 1 //与内存空间分配配合使用 2 int *ptr = (int *)malloc(sizeof(int) * 20); 3 //与计算数组长度配合使用 4 int count = sizeof(darray) / sizeof(double); (4)数组名作为操作数时,将获得整个数组所占空间,当数组名作为实参传递给子函数时,此时数组名已经成为了指针,其计算结果将是指针的所占空间。 void subfunc(double dar...
- 下一篇
几个Python小案例,爱上Python编程!
Python是一种面向对象的解释型编程语言,源代码与解释器CPython遵守GPL协议,Python语法简洁清晰。 语法简洁清晰,那么我们用少量的Python代码能做哪些有趣的东西?温馨提示:文末必看。 一、画爱心表白 1、图形都是由一系列的点(X,Y)构成的曲线,由于X,Y满足一定的关系,所以我们就可以建立模型,建立表达式expression,当满足时,两个for循环(for X in range;for Y in range)就会每行每列的打印。 2、Python代码与注释: 理清思路一行代码实现: 看上去是三行,在IDE中写作一行Python代码即可实现,如图: 此图为静态图,效果并不如意,那么我们把它做成动态图并实现图文表白如何?(可自行更改字母内容) 更改后代码如下: 二、快递查询工具 1、此Python小项目需要用到json与requests两个库,还需调用API。 2、代码注解: 3、查询效果: 三、Python爬虫入门级别教程之图片爬取 1、很多因为兴趣而学习Python的朋友大多是看到别人爬取视频、图片吧,我是以前看到别人爬取小电影,我才有动力学的。 2、Python...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8编译安装MySQL8.0.19
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- Windows10,CentOS7,CentOS8安装Nodejs环境
- Red5直播服务器,属于Java语言的直播服务器
- SpringBoot2整合Redis,开启缓存,提高访问速度
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2配置默认Tomcat设置,开启更多高级功能