Java的锁机制--synchronsized关键字
引言
高并发环境下,多线程可能需要同时访问一个资源,并交替执行非原子性的操作,很容易出现最终结果与期望值相违背的情况,或者直接引发程序错误。
举个简单示例,存在一个初始静态变量count=0,两个线程分别对count进行100000次加1操作,期望的结果是200000,实际是这样的吗?写个程序跑下看看:
1 |
public class CountWithoutSyn { private volatile static int count = 0; public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(2); // 启动线程A |
多次运行上述程序,会发现最终结果可能出现不是200000的情况,如:
1 |
count: 150218 Process finished with exit code 0 |
之所以出现这种情况的原因是,count++不是一个原子性的操作,所谓原子性,说的就是操作不可分割。
count++分为3个步骤:
- 从内存读取count的值;
- 对count值执行+1操作;
- 将count的值写回内存;
比如当前count累加到了101,此时,线程A和B同时拿到了count的值为101,线程A对count加1后将102写回内存,同时线程B也对count加1后将102写回内存,而实际结果应该为103,所以丢失了1次更新。
故高并发环境下,多线程同时对共享变量执行非原子的操作,很容易出现丢失更新的问题。
解决办法很简单,将整个非原子的操作加锁,从而变成原子性的操作就可以了。
Java加锁的方式主要有2种,synchronnized关键字和Lock接口。
下面分别阐述这两种方式,本文先讲解synchronnized。
synchronized
在Java中,每一个对象都有一个锁标记(monitor),也称之为监视器,多线程同时访问某个对象时,线程只有获取了该对象的锁才能访问。
该锁属于典型的互斥锁,即一旦一个线程获取到锁之后,其他线程只能等待。
synchronize关键字可以标记方法或者代码块,当某个线程调用该对象的synchronize方法或者访问synchronize代码块时,该线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,该线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。
对引言中的程序通过synchronized来进行改造:
1 |
public class CountWithSyn { private volatile static int count = 0; public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(2); Object lock = new Object(); // 启动线程A |
多次运行该程序,其结果均是:
1 |
count: 200000 Process finished with exit code 0 |
synchronized代码块使用起来比synchronized方法要灵活得多。因为也许一个方法中只有一部分代码只需要同步,如果此时对整个方法用synchronized进行同步,会影响程序执行效率。而使用synchronized代码块就可以避免这个问题,synchronized代码块可以实现只对需要同步的地方进行同步。
因为上述程序的非原子操作仅是count++,所以synchronized仅修饰count++即可实现线程安全。
1 |
public class CountWithSyn { private volatile static int count = 0; public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(2); Object lock = new Object(); // 启动线程A |
需要注意的是:
当一个线程正在访问一个对象的synchronized方法,那么其他线程不能访问该对象的其他synchronized方法。这个原因很简单,因为一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他synchronized方法。
当一个线程正在访问一个对象的synchronized方法,那么其他线程能访问该对象的非synchronized方法。这个原因很简单,访问非synchronized方法不需要获得该对象的锁,假如一个方法没用synchronized关键字修饰,说明它不会使用到临界资源,那么其他线程是可以访问这个方法的,
如果一个线程A需要访问对象object1的synchronized方法fun1,另外一个线程B需要访问对象object2的synchronized方法fun1,即使object1和object2是同一类型),也不会产生线程安全问题,因为他们访问的是不同的对象,所以不存在互斥问题。
那么,synchronized关键字底层是如何实现的呢?反编译它的字节码看一下,如下述代码的字节码为:
1 |
public class SynCode { private Object lock = new Object(); public void method1(){ |
从反编译获得的字节码可以看出,synchronized代码块实际上多了monitorenter和monitorexit两条指令。monitorenter指令执行时会让对象的锁计数加1,而monitorexit指令执行时会让对象的锁计数减1,其实这个与操作系统里面的PV操作很像,操作系统里面的PV操作就是用来控制多个线程对临界资源的访问。
对于synchronized方法,执行中的线程识别该方法的method_info结构是否有ACC_SYNCHRONIZED标记设置,然后它自动获取对象的锁,调用方法,最后释放锁。如果有异常发生,线程自动释放锁。
对于synchronized方法或者synchronized代码块,当出现异常时,JVM会自动释放当前线程占用的锁,因此不会由于异常导致出现死锁现象。
参考文献:
- https://www.cnblogs.com/dolphin0520/p/3923737.html
- https://www.cnblogs.com/shixm/p/5490026.html
- https://www.cnblogs.com/xiaoxi/p/9140541.html
欢迎您扫一扫上面的二维码,关注我的微信公众号!
Java的锁机制--synchronsized关键字的更多相关文章
- java的锁机制
一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在Java里边就是拿到某个同步对象的锁(一个对象只有一把锁): 如果这个时候同步对象的锁被其他线程拿走了,他(这个线 ...
- Java常用锁机制简介
在开发Java多线程应用程序中,各个线程之间由于要共享资源,必须用到锁机制.Java提供了多种多线程锁机制的实现方式,常见的有synchronized.ReentrantLock.Semaphore. ...
- lesson3:java的锁机制原理和分析
jdk1.5之前,我们对代码加锁(实际是对象加锁),都是采用Synchronized关键字来处理,jdk1.5及以后的版本中,并发编程大师Doug Lea在concurrrent包中提供了Lock机制 ...
- [java多线程] - 锁机制&同步代码块&信号量
在美眉图片下载demo中,我们可以看到多个线程在公用一些变量,这个时候难免会发生冲突.冲突并不可怕,可怕的是当多线程的情况下,你没法控制冲突.按照我的理解在java中实现同步的方式分为三种,分别是:同 ...
- java的锁机制——synchronized
一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在java里边就是拿到某个同步对象的锁(一个对象只有一把锁): 如果这个时候同步对象的锁被其他线程拿走了,他(这个线 ...
- 深入浅出 Java Concurrency 锁机制 : AQS
转载:http://www.blogjava.net/xylz/archive/2010/07/06/325390.html 在理解J.U.C原理以及锁机制之前,我们来介绍J.U.C框架最核心也是最复 ...
- [置顶] 深入探析Java线程锁机制
今天在iteye上提了一个关于++操作和线程安全的问题,一位朋友的回答一言点醒梦中人,至此我对Java线程锁有了更加深刻的认识.在这里也做个总结供大家参考. 先看几段代码吧! 代码一: public ...
- Java 线程锁机制 -Synchronized Lock 互斥锁 读写锁
(1)synchronized 是互斥锁: (2)ReentrantLock 顾名思义 :可重入锁 (3)ReadWriteLock :读写锁 读写锁特点: a)多个读者可以同时进行读b)写者必须互斥 ...
- Java各种锁机制简述
线程安全是多线程领域的问题,线程安全可以简单理解为一个方法或者一个实例可以在多线程环境中使用而不会出现问题. 在 Java 多线程编程当中,提供了多种实现 Java 线程安全的方式: 最简单的方式,使 ...
随机推荐
- 洛谷 P5017 摆渡车
题目传送门 解题思路: 个人感觉DP这东西,只可意会,不可言传 AC代码: #include<iostream> #include<cstdio> #include<cs ...
- BZOJ4059[Cerc2012]Non-boring sequences(扫描线/分治)
这题正解应该是扫描线,就是发现DP的区间在两个维度都为连续段,于是可以直接扫描线.但不幸的是,扫描线常数过大,无法通过本题. 考虑分治.对于分治区间[l,r],可以记录pre和nxt表示其前/后一次出 ...
- arg min,arg max, e.g ,i.e
数学中常见的arg min,arg max 是什么意思 arg 是变元(即自变量argument)的英文缩写 arg min 就是使后面这个式子到达最小值时的变量的取值 arg max 就是使后面这个 ...
- [原]win10开机时开启NumLock
修改如下注册表项下的InitialKeyboardIndicators的值为80000002,重启即可. HKEY_USERS\.Default\Control Panel\Keyboard\ HKE ...
- maven 设置pom 指定jdk版本
<profile> <id>jdk-1.8</id> <activation> <activeByDefault>true</acti ...
- 透过F5获取服务器真实内网IP
渗透测试过程中,经常会遇到目标服务器使用F5 LTM做负载均衡. 如果能获取到目标服务器的真实IP地址,会给后续渗透带来一定便利. 本文既是最近渗透遇到的一点点经验分享. F5修改cookie机制 F ...
- 初识OpenGl
函数命名规则 OpenGl函数都遵循一个命名约定:<库前缀> <根命令> <可选参数个数> <可选参数类型> 如:glColor3f() ,gl:核心库 ...
- 什么是CDN
1.什么是cdn cdn全称是内容分发网络.其目的是让用户能够更快速的得到请求的数据.简单来讲,cdn就是用来加速的,他能让用户就近访问数据,这样就更更快的获取到需要的数据.举个例子,现在服务器 ...
- 实战_3:新建产品配置(product)并导出项目
产品配置Product 产品配置用于定义和管理RCP应用的多个方面特征.并支持将RCP项目导出为部署包(类似eclipse压缩包),可以直接部署到其他环境上使用. 产品配置必须新建一个 扩展名为 .p ...
- jmeter测试get post 笔记
0 环境 系统环境:win7 1 操作 1 post 新建线程组 2 get 和post新建类似 http请求 只是新建一个参数化我测试的2个url http://127.0.0.1:8080/cry ...