解决多线程并发安全问题
解决多线程的并发安全问题,java无非就是加锁,具体就是两个方法
(1) Synchronized(java自带的关键字)
(2) lock 可重入锁 (可重入锁这个包java.util.concurrent.locks 底下有两个接口,分别对应两个类实现了这个两个接口:
(a)lock接口, 实现的类为:ReentrantLock类 可重入锁;
(b)readwritelock接口,实现类为:ReentrantReadWriteLock 读写锁)
也就是说有三种:
(1)synchronized 是互斥锁;
(2)ReentrantLock 顾名思义 :可重入锁
(3)ReentrantReadWriteLock :读写锁
读写锁特点:
a)多个读者可以同时进行读
b)写者必须互斥(只允许一个写者写,也不能读者写者同时进行)
c)写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)
总结来说,Lock和synchronized有以下几点不同:
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率。
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
首先看一下Synchronized的原理:
1、synchronized
把代码块声明为 synchronized,有两个重要后果,通常是指该代码具有 原子性(atomicity)和 可见性(visibility)。
(1) 原子性
原子性意味着个时刻,只有一个线程能够执行一段代码,这段代码通过一个monitor object保护。从而防止多个线程在更新共享状态时相互冲突。
(2) 可见性
可见性则更为微妙,它要对付内存缓存和编译器优化的各种反常行为。啥是可见性呢?
答:它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 。
作用:如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。
一般来说,线程以某种不必让其他线程立即可以看到的方式(不管这些线程在寄存器中、在处理器特定的缓存中,还是通过指令重排或者其他编译器优化),不受缓存变量值的约束,但是如果开发人员使用了同步,那么运行库将确保某一线程对变量所做的更新先于对现有synchronized
块所进行的更新,当进入由同一监控器(lock)保护的另一个synchronized
块时,将立刻可以看到这些对变量所做的更新。类似的规则也存在于volatile
变量上。
——volatile只保证可见性,不保证原子性!
(3)synchronize的限制:
- 当线程尝试获取锁的时候,如果获取不到锁会一直阻塞, 它无法中断一个正在等候获得锁的线程;
- 如果获取锁的线程进入休眠或者阻塞,除非当前线程异常,否则其他线程尝试获取锁必须一直等待,也无法通过投票得到锁,如果不想等下去,也就没法得到锁。
2、ReentrantLock (可重入锁)
可重入的意思是某一个线程是否可多次获得一个锁,在继承的情况下,如果不是可重入的,那就形成死锁了,比如递归调用自己的时候;,如果不能可重入,每次都获取锁不合适,比如synchronized就是可重入的,ReentrantLock也是可重入的
锁的概念就不用多解释了,当某个线程A已经持有了一个锁,当线程B尝试进入被这个锁保护的代码段的时候.就会被阻塞.而锁的操作粒度是”线程”,而不是调用(至于为什么要这样,下面解释).同一个线程再次进入同步代码的时候.可以使用自己已经获取到的锁,这就是可重入锁java里面内置锁(synchronize)和Lock(ReentrantLock)都是可重入的
我自己写了个例子:
package entrantlock_test;import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;class parent {protected Lock lock=new ReentrantLock();public void test(){lock.lock();try{System.out.println("Parent");}finally{lock.unlock();}}}class Sub extends parent{@Overridepublic void test() {// TODO Auto-generated method stub lock.lock();try{super.test();System.out.println("Sub");}finally{lock.unlock();}}}public class LockTest{public static void main(String[] args){Sub s=new Sub();s.test();} }
View Code
2.1 . 为什么要可重入
如果线程A继续再次获得这个锁呢?比如一个方法是synchronized,递归调用自己,那么第一次已经获得了锁,第二次调用的时候还能进入吗? 直观上当然需要能进入.这就要求必须是可重入的.可重入锁又叫做递归锁,不然就死锁了。
它实现方式是:
为每个锁关联一个获取计数器和一个所有者线程,当计数值为0的时候,这个所就没有被任何线程只有.当线程请求一个未被持有的锁时,JVM将记下锁的持有者,并且将获取计数值置为1,如果同一个线程再次获取这个锁,技术值将递增,退出一次同步代码块,计算值递减,当计数值为0时,这个锁就被释放.ReentrantLock里面有实现
其实也有不可重入锁:这个还真有.Linux下的pthread_mutex_t锁是默认是非递归的。可以通过设置PTHREAD_MUTEX_RECURSIVE属性,将pthread_mutex_t锁设置为递归锁。如果要自己实现不可重入锁,同可重入锁,这个计数器只能为1.或者0,再次进入的时候,发现已经是1了,就进行阻塞.jdk里面没有默认的实现类.
Java.util.concurrent.lock
中的Lock
框架是锁定的一个抽象,Lock弥补了synchronized的局限,提供了更加细粒度的加锁功能。
ReentrantLock
类是唯一实现了Lock的类
,它拥有与synchronized
相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)
用sychronized修饰的方法或者语句块在代码执行完之后锁自动释放,而是用Lock需要我们手动释放锁,所以为了保证锁最终被释放(发生异常情况),要把互斥区放在try内,释放锁放在finally内!!
Lock 接口api如下
void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; void unlock(); Condition newCondition();
其中最常用的就是lock和unlock操作了。因为使用lock时,需要手动的释放锁,所以需要使用try..catch来包住业务代码,并且在finally中释放锁。典型使用如下
private Lock lock = new ReentrantLock();public void test(){lock.lock();try{doSomeThing();}catch (Exception e){// ignored}finally {lock.unlock();} }
解决多线程并发安全问题相关推荐
- JAVE SE 学习day_09:sleep线程阻塞方法、守护线程、join协调线程同步方法、synchronized关键字解决多线程并发安全问题
一.sleep线程阻塞方法 static void sleep(long ms) Thread提供的静态方法sleep可以让运行该方法的线程阻塞指定毫秒,超时后线程会自动回到RUNNABLE状态,等待 ...
- 多线程并发安全问题与线程锁
一.多线程并发安全问题 二.什么是线程锁及分类 三.synchronized关键字 多线程并发安全问题 当多个线程并发操作同一临界资源,由于线程切换时机不确定,导致操作临界资源的顺序出现混乱严重时可能 ...
- Java-多线程-Future、FutureTask、CompletionService、CompletableFuture解决多线程并发中归集问题的效率对比
转载声明 本文大量内容系转载自以下文章,有删改,并参考其他文档资料加入了一些内容: [小家Java]Future.FutureTask.CompletionService.CompletableFut ...
- C#使用读写锁三行代码简单解决多线程并发写入文件时线程同步的问题
在开发程序的过程中,难免少不了写入错误日志这个关键功能.实现这个功能,可以选择使用第三方日志插件,也可以选择使用数据库,还可以自己写个简单的方法把错误信息记录到日志文件. 选择最后一种方法实现的时候, ...
- 如何解决多线程并发问题
多线程编程中的三个核心概念 原子性 这一点,跟数据库事务的原子性概念差不多,即一个操作(有可能包含有多个子操作)要么全部执行(生效),要么全部都不执行(都不生效). 关于原子性,一个非常经典的例子就是 ...
- 如何解决多线程并发访问一个资源的安全性问题?
原子操作:所谓原子操作是指不会被线程调度机制打断的操作:这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切[1] 换到另一个线程). 关于我对原子操作的理解:原子操 ...
- Java多线程:多线程同步安全问题的 “三“ 种处理方式 ||多线程 ”死锁“ 的避免 || 单例模式”懒汉式“的线程同步安全问题
Java多线程:多线程同步安全问题的 "三" 种处理方式 ||多线程 "死锁" 的避免 || 单例模式"懒汉式"的线程同步安全问题 每博一文 ...
- java多线程并发及线程池
线程的常用创建方式 1.继承Thread类创建线程类 public class FirstThreadTest extends Thread {public void run(){System.out ...
- 《多线程并发任务处理组件》序章——生活不能就这样悲泣
背景 入行也有些日子, 最近突然心中迸发出一个想法, 想要去解决多线程并发环境的一些问题. 并不是说现在社区找不到优秀的这方面的开源项目, 更多的是想自己动手做一些东西出来, 毕竟性格一直在驱使着我要 ...
最新文章
- 发布文件打包springboit_程序安装包咋制作的?Qt程序打包三部曲,从应用程序到安装包...
- R语言plotly可视化:plotly可视化互相重叠的归一化直方图并在直方图中添加密度曲线kde、设置不同的直方图使用不同的分箱大小(bin size)、在直方图的边缘添加边缘轴须图
- 设计一条简单的等待工作队列之软件模型设计与实现(二)
- SpringMVC 框架系列之初识与入门实例
- 安卓上比较好的python开发软件-手机随时随地写Python,还可以开发安卓APP,太厉害了!...
- 某中国500强企业BI系统成功应用案例
- opencv-4.1.0-百度云盘下载链接-环境配置
- tabulate matlab,matlab中用于统计矩阵数据频率,出现次数的函数
- linux下的redis配置;
- window.open实现post方式复杂参数传递
- 什么是IoT物联网平台,以及如何做平台选型
- IT系统风险管理体系的构建思路
- Flutter 修改APP名称和logo
- 使用Java实现的高精度科学计算器
- 去掉小数点后多余的0
- 从零开始用android studio
- 强主动性的人,如何做事一杠子到底?
- ios 添加浮动效果_IOS 实现3D 浮动效果动画
- html5实习体会,html5实习心得体会.docx
- android 解压加密zip,zip压缩解压加密器