锁和synchronized
锁的常见概念
- 互斥: 同一时刻只有一个线程执行
- 临界区:一段需要互斥执行的代码
- 细粒度锁: 用不同的锁对受保护资源进行精细化管理。 细粒度锁可以提高并行度,是性能优化的一个重要手段
- 死锁 :一组互相竞争资源的线程因互相等待,导致“永久”阻塞的现象 。
synchronized
class X{
//修饰非静态方法
synchronized void foo(){
//临界区
}
//修饰静态方法
synchronized static void bar(){
//临界区
}
//修饰代码块
Object obj = new Object();
void baz(){
synchronized(obj){
//临界区
}
}
}
Java编译器会在synchronized修饰的方法或代码块前后自动加上加锁lock()和解锁unlock(),这样做的好处就是加锁lock()和解锁unlock()一定 是成对出现的,毕竟忘记解锁unlock()可是个致命的Bug(意味着其他线程只能死等下去了)。
修饰静态方法:
//修饰静态方法是用当前类的字节码文件作为锁
class X{
//修饰静态方法
synchronized(X.class) static void bar(){
//临界区
}
}
修饰非静态方法:
//修饰非静态方法是用当前对象作为锁
class X{
//修饰非静态方法
synchronized(this) static void bar(){
//临界区
}
}
如何用一把锁保护多个资源
受保护资源和锁之间合理的关联关系应该是N:1的关系,也就是说可以用一把锁来保护多个资源,但是不能用多把锁来保护一个资源,
使用锁的正确姿势
依转账业务作为示例
示例一:
public class Account {
/**
*锁:保护账⼾余额
*/
private final Object balLock = new Object();
/**
* 账⼾余额
*/
private Integer balance;
/**
* 错误的做法
* 非静态方法的锁是this,
* this这把锁可以保护自己的余额this.balance,保护不了别人的余额 target.balance
*
*/
synchronized void transfer(Account target,int amt){
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;//这段代码会出现线程安全,要保证线程安全的话要使用同一个锁
}
}
}
示例二:
public class Account {
/**
*锁:保护账⼾余额
*/
private final Object balLock = new Object();
/**
* 账⼾余额
*/
private Integer balance;
/**
* 正确的做法,但是会导致整个转账系统的串行
*
* Account.class是所有Account对象共享的,
* 而且这个对象是Java虚拟机在加载Account类的时候创建的,
* 所以我们不用担心它的唯一性
*
* 这样还有个弊端:所有的转账都是串行了
*/
void transfer2(Account target,int amt){
synchronized(Account.class){
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;
}
}
}
}
这样的话转账操作就成了串行的了,正常的逻辑应该只锁转入账号和被转入账户;不影响其他的转账操作。稍作改造:
示例三:
public class Account {
/**
*锁:保护账⼾余额
*/
private final Object lock;
/**
* 账⼾余额
*/
private Integer balance;
//私有化无参构造
private Account(){}
//设置一个传递lock的有参构造
private Account(Object lock){
this.lock = lock;
}
/**
* 转账
*/
void transfer(Account target,int amt){
//此处检查所有对象共享锁
synchronized(lock){
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;
}
}
}
}
这个方法虽然能够解决问题,但是它要求创建Account对象的时候必须传入同一个对象,
还有就是传递对象过于麻烦,写法繁琐缺乏可行性。
示例四:
public class Account {
/**
* 账⼾余额
*/
private Integer balance;
/**
* 转账
*/
void transfer(Account target,int amt){
//此处检查所有对象共享锁
synchronized(Account.class){
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;
}
}
}
}
用Account.class作为共享的锁,锁定的范围太大。 Account.class是所有Account对象共享的,而且这个对象是Java虚拟机在加载Account类的时候创建的,所以我们不用担心它的唯一性。使用Account.class作为共享的锁,我们就无需在创建Account对象时传入了。
这样新的问题就出来了虽然用Account.class作为互斥锁,来解决银行业务里面的转账问题,虽然这个方案不存在 并发问题,但是所有账户的转账操作都是串行的,例如账户A转账户B、账户C转账户D这两个转账操作现实 世界里是可以并行的,但是在这个方案里却被串行化了,这样的话,性能太差。所以如果考虑并发量这种方法也不行的
正确的写法是这样的(使用细粒度锁):
示例五:
public class Account {
/**
* 账⼾余额
*/
private Integer balance;
/**
* 转账
*/
void transfer(Account target,int amt){
//锁定转出账户
synchronized(this){
//锁住转入账户
synchronized(target){
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;
}
}
}
}
}
我们试想在古代,没有信息化,账户的存在形式真的就是一个账本,而且每个账户都有一个账本,这些账本 都统一存放在文件架上。银行柜员在给我们做转账时,要去文件架上把转出账本和转入账本都拿到手,然后做转账。这个柜员在拿账本的时候可能遇到以下三种情况:
- 文件架上恰好有转出账本和转入账本,那就同时拿走;
- 如果文件架上只有转出账本和转入账本之一,那这个柜员就先把文件架上有的账本拿到手,同时等着其 他柜员把另外一个账本送回来;
- 转出账本和转入账本都没有,那这个柜员就等着两个账本都被送回来。
细粒度锁有可能会出现死锁
- 死锁 :一组互相竞争资源的线程因互相等待,导致“永久”阻塞的现象 。
- 两个线程彼此拿着对方的资源都不释放就会导致死锁,
- 使用细粒度锁可能会导致死锁
如果有客户找柜员张三做个转账业务:账户 A转账户B 100元,此时另一个客户找柜员李四也做个转账业务:账户B转账户A 100元,于是张三和李四同时都去文件架上拿账本,这时候有可能凑巧张三拿到了账本A,李四拿到了账本B。张三拿到账本A后就等着 账本B(账本B已经被李四拿走),而李四拿到账本B后就等着账本A(账本A已经被张三拿走),他们要等 多久呢?他们会永远等待下去…因为张三不会把账本A送回去,李四也不会把账本B送回去。我们姑且称为死等吧。
如何避免死锁
- 互斥,共享资源X和Y只能被一个线程占用;
- 占有且等待,线程T1已经取得共享资源X,在等待共享资源Y的时候,不释放共享资源x;
- 不可抢占,其他线程不能强行抢占线程T1占有的资源;
- 循环等待,线程1等待线程T2占有的资源,线程T2等待线程T1占有的资源,就是循环等待。
只要破坏其中一个就可以避免死锁
等待-通知机制
用synchronized实现等待-通知机制
- synchronized 配合wait(),notif(),notifyAll()这三个方法能够轻松实现.
- wait(): 当前线程释放锁,进入阻塞状态
- notif(),notifAll(): 通知阻塞的线程有可以继续执行,线程进入可执行状态
- notif()是会随机地地通知等待队歹一个线程
- notifyAll()会通知等待队列中的所有线程,建议使用notifAll()
wait与sleep区别:
sleep是Object的中的方法,wait是Thread中的方法
wait会释放锁,sleep不会释放锁
wait需要用notif唤醒,sleep设置时间,时间到了唤醒
wait无需捕获异常,而sleep需要
wait(): 当前线程进入阻塞
**** 码字不易如果对你有帮助请给个关注****
**** 爱技术爱生活 QQ群: 894109590****
锁和synchronized的更多相关文章
- Java 锁机制 synchronized
转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/75126630 本文出自[赵彦军的博客] 1.前言 在多线程并发编程中Synchro ...
- ReenTrantLock可重入锁和synchronized的区别
ReenTrantLock可重入锁和synchronized的区别 可重入性: 从名字上理解,ReenTrantLock的字面意思就是再进入的锁,其实synchronized关键字所使用的锁也是可重入 ...
- 码农会锁,synchronized 对象头结构(mark-word、Klass Pointer)、指针压缩、锁竞争,源码解毒、深度分析!
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 感觉什么都不会,从哪开始呀! 这是最近我总能被问到的问题,也确实是.一个初入编程职场 ...
- java多线程(三)——锁机制synchronized(同步语句块)
用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法之行一个长时间的任务,那么B线程必须等待比较长的时间,在这样的情况下可以使用synchronized同步语句快来解 ...
- java多线程(二)——锁机制synchronized(同步方法)
synchronized Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码.当两个并发线程访问同一个对象object中 ...
- Java Learning:并发中的同步锁(synchronized)
引言 最近一段时间,实验室已经倾巢出动找实习了,博主也凑合了一把,结果有悲有喜,BAT理所应当的跪了,也收到了其他的offer,总的感受是有必要夯实基础啊. 言归正传,最近在看到java多线程的时候, ...
- Java 线程锁机制 -Synchronized Lock 互斥锁 读写锁
(1)synchronized 是互斥锁: (2)ReentrantLock 顾名思义 :可重入锁 (3)ReadWriteLock :读写锁 读写锁特点: a)多个读者可以同时进行读b)写者必须互斥 ...
- 多线程里面的关键字,wait, notfiy, 锁(synchronized), lock接口
多线程环境下,必须考虑线程同步的问题,这是因为多个线程同时访问变量或者资源时会有线程争用,比如A线程读取了一个变量,B线程也读取了这个变量,然后他们同时对这个变量做了修改,写回到内存中,由于是同时做修 ...
- 浅谈Java中的锁:Synchronized、重入锁、读写锁
Java开发必须要掌握的知识点就包括如何使用锁在多线程的环境下控制对资源的访问限制 ◆ Synchronized ◆ 首先我们来看一段简单的代码: 12345678910111213141516171 ...
- Java线程锁,synchronized、wait、notify详解
(原) JAVA多线程这一块有点绕,特别是对于锁,对锁机制理解不清的话,程序出现了问题也很难找到原因,在此记录一下线程的执行以及各种锁. 1.JAVA中,每个对象有且只有一把锁(lock),也叫监视器 ...
随机推荐
- 9、数组中删除元素(test6.java)
前文讲到,通过函数,进行数组元素的添加,这里同样通过这个函数,进行数组的删除. 举个例子,代码如下: //导入输入所需要的包 import java.util.Scanner; public clas ...
- java虚拟机学习笔记(五)---运行时的数据区域
Java虚拟机所管理的内存包括以下几个运行时的数据区域:方法区,堆,虚拟机栈,本地方法栈,程序计数器.下面对其进行介绍: 程序计数器 它是一块较小的内存空间,可以看做当前线程做执行的字节码的信号指示器 ...
- Spring入门(九):运行时值注入
Spring提供了2种方式在运行时注入值: 属性占位符(Property placeholder) Spring表达式语言(SpEL) 1. 属性占位符 1.1 注入外部的值 1.1.1 使用Envi ...
- tk.mybatis扩展通用接口
一.tk.mybatis已经为我们封装好了许多拆箱即用的通用mapper,但在实际的项目开发中想必不少小伙伴在数据库设计中都会采用逻辑删除这种方案,再去使用通用的mapper接口就不行了.这时候就需要 ...
- (五)c#Winform自定义控件-复选框
前提 入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章. 开源地址:https://gitee.com/kwwwvagaa/net_winform_custom_control ...
- https理论及实践
什么是https协议? http协议以明文的方式在网络中传输,安全性难以保证,https在http协议的基础上加入SSL/TLS层.TLS是SSL协议的最新版本,SSL使用SSL数字证书在通信两端建立 ...
- Redis总结(九)Linux环境如何安装redis
以前总结Redis 的一些基本的安装和使用,由于是测试方便,直接用的window 版的reids,并没有讲redis在linux下的安装.今天就补一下Linux环境如何安装redis. 大家可以这这里 ...
- vue 使用gojs绘制简单的流程图
在vue项目中需要展示工作流进度,可以使用的流程图插件很多 flowchart.js http://adrai.github.io/flowchart.js/ , 基于SVG创建Flow Chart ...
- .NET中使用WebService,以及和一般处理程序、类库的区别
首先我们来看一下如何创建Web Service 首先在解决方案中新建项,选择ASP.NETWeb应用程序 然后选择一个空的项目就可以,单击确定 项目建完之后,在项目上右键-->添加-->新 ...
- LoRaWAN_stack移植笔记(七)_数据包的接收发送
以下的代码适用于LoRa sx1276点对点的通讯,纯粹的考虑在非发射模式下即为接收模式 配置sx1276的射频参数,并且切换到接收模式 //bandwidth [0:125 1:250 2:500] ...