java 加锁_Java并发之synchronized深入

217a8bfca1650093c9a873f4f2e6d620.png

一句话总结synchronized:

JVM会自动通过使用monitor来加锁和解锁,保证了同时只有一个线程可以执行指定代码,从而保证了线程安全,同时具有可重入和不可中断的性质。

一.synchronized的作用

使用synchronized修饰方法或者代码块时,它能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果。

二.synchronized的地位

1.Synchronized是Java的关键字,被Java语言原生支持

2.是最基本的互斥同步手段

3.是并发编程中的元老级角色,是并发编程的主要内容

三.synchronized的性质

synchronized的具有可重入性和不可中断性

1.可重入性(synchronized区别于其他锁的一个很重要的性质)

什么是可重入:指的是同一线程的外层函数获得锁之后,内层函数可以直接再次获取该锁。也叫做递归锁。Java中两大递归锁:Synchronized和ReentrantLock。

锁的不可重入性:线程拿到一把锁了,如果想再次使用这把锁,必须先将锁释放,与其他线程再次进行竞争。

锁的可重入性:如果线程已经拿到了锁,试图去请求这个已经获得到的锁,而无需提前释放,直接就可以使用手里的这把锁,这就叫做可重入性

synchronized是可重入锁

可重入锁的好处:

1 避免死锁

2 提升封装性

粒度:

可重入的特性是线程级别的,不是调用级别的。

情况1:证明同一个方法是可重入的

实例代码:

public class SynchronizedRecursion {

int a = 0;

public static void main(String[] args) {

SynchronizedRecursion sr = new SynchronizedRecursion();

sr.method1();

}

private synchronized void method1(){

System.out.println(synchronized修饰的方法,a = + a);

if(a==0){

a++;

method1();

}

}

}

情况2:证明可重入不要求是同一个方法

实例代码:

public class SynchronizedOtherMethod {

public synchronized void method1(){

System.out.println(我是method1);

method2();

}

public synchronized void method2(){

System.out.println(我是method2);

}

public static void main(String[] args) {

SynchronizedOtherMethod som = new SynchronizedOtherMethod();

som.method1();

}

}

情况3:证明可重入不要求是同一个类中的

实例代码:

public class SynchronizedSuperClass {

public synchronized void doSomething(){

System.out.println(我是父类方法);

}

}

class TestClass extends SynchronizedSuperClass{

public synchronized void doSomething(){

System.out.println(我是子类方法);

super.doSomething();

}

public static void main(String[] args) {

TestClass tc = new TestClass();

tc.doSomething();

}

}

2.不可中断(相比于其他有的锁可以中断,这个性质是synchronized的一个劣势所在)

一旦这个锁已经被别的线程获得了,如果当前线程还想获得,只能选择等待或者阻塞,直到别的线程释放这个锁。如果别的线程 永远不释放锁,那么线程只能永远地等下去。

相比之下,Lock类,可以拥有中断的能力。

第一点,如果我觉得我等的时候太长了,有权中断现在已经获取到锁的线程的执行; 第二点,如果我觉得我等待的时间太长了不想再等了,也可以退出。

Lock比synchronized灵活很多,但是编码易出错。

四.synchronized的原理

1.加解锁原理:现象,时机,深入JVM看字节码

现象:每一个类的实例对应一把锁,而每一个synchronized方法都必须先获得调用该方法的类的实例的锁方能执行,否则线程会阻塞。而方法一旦执行,它就独占了这把锁,直到该方法返回或者是抛出异常,才将锁释放。一旦锁释放之后,之前被阻塞的线程才能获得这把锁,从被阻塞的状态重新进入到可执行的状态。当一个对象中有synchronized修饰的方法或者代码块的时候,要想执行这段代码,就必须先获得这个对象锁,如果此对象的对象锁已经被其他调用者所占用,就必须等待它被释放。所有的Java对象都含有一个互斥锁,这个锁由JVM自动去获取和释放,我们只需要指定这个对象就行了,至于锁的释放和获取不需要我们操心。

获取和释放锁的时机:内置锁

我们知道每一个Java对象都可以用作一个同步的锁,这个锁叫做内部锁,或者叫做监视器锁--monitor lock。线程在进入到同步代码块之前,会自动获得这个锁,并且在退出同步代码块的时候会自动的释放这个锁,无论是正常途径退出还是抛出异常退出。获得这个内置锁的唯一途径就是进入这个锁保护的同步代码块或者同步方法中。

实例代码:

public class SynchronizedToLock {

Lock lock = new ReentrantLock();

public synchronized void method1(){

System.out.println(我是synchronized锁);

}

public void method2(){

lock.lock(); //加锁

try{

System.out.println(我是lock锁);

}finally {

lock.unlock(); //释放锁

}

}

public static void main(String[] args) {

SynchronizedToLock stl = new SynchronizedToLock();

stl.method1();

stl.method2();

}

}

method1()和method2()方法等价

深入JVM看字节码:

实例代码:

public class Decompilation {

private Object object = new Object();

//同步代码块

public void insert(Thread thread){

synchronized (object){

}

}

}

1、将上面的Java代码编译为 .class文件,命令窗口执行命令:javac xxx.java 进行编译。

2、通过反编译查看字节码,执行命令:javap -verbose xxx.class 进行反编译

3、synchronized如何实现的,有个加锁monitorenter和解锁monitorexit

读到该指令,会让monitor计数器+1或-1

具体如图:

bbcb005a1bff5cb189a7ac85a55aeb0a.png
8ee56d6e31b888b310316f00c3539a63.png

线程既可以在方法完成之后退出,也可以在抛出异常后退出,因此monitorexit数量多于monitorenter。

monitorenter和monitorexit指令

monitorenter:线程每次进入时,计数器+1。如果重入,继续加

monitorexit:线程退出时,计数器-1.变为0时候,其他线程可以获取锁。

2.可重入原理:加锁次数计数器

1、JVM负责跟踪对象被加锁的次数;

2、有个monitor计数器,线程第一次给对象加锁的时候,计数变为1.每当这个相同的线程在此对象上再次获得锁时,计数会递增。

3、任务结束离开,则会执行monitorexit,计数递减,直到完全释放

3.可见性原理:Java内存模型

dd7eec67c636a0c7e20327e66eb4a9cb.png

五.synchronized的缺陷

1.效率低:

· 锁的释放情况少

· 试图获得锁时不能设定超时

· 不能中断一个正在试图获得锁的线程

2.不够灵活(读写锁更灵活:读操作的时候不会加锁,写操作的时候才会加锁):

· 加锁和释放的时机单一

· 每个锁仅有单一的条件(某个对象),可能是不够的

3.无法知道是否成功获取到锁,而使用Lock则不同。

Lock可以,如果尝试成功了做一些逻辑判断,如果没有成功做另外一些逻辑判断.

Lock类:

lock.lock();lock.unlock();通过这两个方法,可以手动加锁和解锁。

lock.tryLock();lock.tryLock(10, TimeUnit.MINUTES);可以判断是否加锁,以及设置超时时间,返回类型为boolean

六.synchronized的用法

1、对象锁:

a、方法锁(默认锁对象为this当前实例对象):synchronized修饰普通方法,锁对象默认为this

实例代码:

public synchronized void run(){

for(int j = 0; j 100000; j++){

i++;

}

}

b、同步代码块锁: 自己手动指定锁对象

实例代码:

public synchronized void run(){

synchronized (this){

for(int j = 0; j 100000; j++){

i++;

}

}

}

2、类锁:

Java类可能有很多个对象,但只要一个class对象。类锁只不过是Class对象的锁而已。类锁只能在同一时刻被一个对象拥有。

a、使用关键字synchronized修饰静态static的方法

实例代码:

public void run(){

method();

}

public static synchronized void method(){

for(int j = 0; j 100000; j++){

i++;

}

}

b、使用关键字synchronized指定锁为Class对象: synchronized(*.class)

实例代码:

public synchronized void run(){

synchronized (DisappearRequest2.class){

for(int j = 0; j 100000; j++){

i++;

}

}

}

synchronized使用的注意点:

锁的信息是保存在对象头中的、作用域不易过大,影响性能、避免死锁

七.不使用并发情况会带来的后果以及解决办法

问题:两个线程同时a++,最后结果会比预计的少

原因

count++,它看上去只是一个操作,实际上包含了三个动作:

1.读取count

2.将count加一

3.将count的值写入到内存中

这三个操作,如果不按照原子去执行,就会带来并发问题

解决之前:

public class DisappearRequest1 implements Runnable{

static DisappearRequest1 instance = new DisappearRequest1();

static int i = 0;

@Override

public void run(){

for(int j = 0; j 100000; j++){

i++;

}

}

public static void main(String[] args) throws InterruptedException {

Thread t1 = new Thread(instance);

Thread t2 = new Thread(instance);

t1.start();

t2.start();

t1.join(); //等待线程结束

t2.join();

System.out.println(i= + i);

}

}

解决:

public class DisappearRequest2 implements Runnable{

static DisappearRequest2 instance = new DisappearRequest2();

static int i = 0;

//方法1:直接把synchronized加到方法上

/* public synchronized void run(){

for(int j = 0; j 100000; j++){

i++;

}

}*/

//方法2:对象锁的同步代码块

/*public synchronized void run(){

synchronized (this){

for(int j = 0; j 100000; j++){

i++;

}

}

}*/

//第三种:类锁:synchronized(*.class)

public synchronized void run(){

synchronized (DisappearRequest2.class){

for(int j = 0; j 100000; j++){

i++;

}

}

}

//第四种:类锁

/*public void run(){

method();

}

public static synchronized void method(){

for(int j = 0; j 100000; j++){

i++;

}

}*/

public static void main(String[] args) throws InterruptedException {

Thread t1 = new Thread(instance);

Thread t2 = new Thread(instance);

t1.start();

t2.start();

t1.join(); //等待线程结束

t2.join();

System.out.println(i= + i);

}

}

八.关于多线程访问同步方法的七种情况的总结

1.两个线程同时访问一个对象的同步方法(对象锁):串行执行

这种情况就是对象锁的方法锁情况。会相互等待,只能有一个线程持有锁。

2.两个线程访问的是两个对象的同步方法:两个锁对象互不干扰,并行执行

不会加锁,因为访问的是不同的实例

3.两个线程访问的是synchronized的静态方法:两个锁对象是同一个锁,串行执行

这种情况就是类锁的静态方法锁。

4.同时访问同步方法与非同步方法:同步(并行)执行

synchronized关键字只作用于当前方法,不会影响其他未加关键字的方法的并发行为。因此非同步方法不受到影响,还是会并发执行。

5.访问同一个对象的不同的普通同步方法(对象锁):串行执行

synchronized关键字虽然没有指定所要的那个锁对象,但是本质上是指定了this这个对象作为它的锁。所以对于同一个实例来讲,两个方法拿到的是同一把锁,因此会出现串行的情况。

6.同时访问静态synchronized和非静态synchronized方法:同步(并行)执行

前者为类锁,锁为Class类;后者为对象锁,锁为this对象。因此两者的锁不同,会并行执行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/454279.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

激活函数之 Sigmoid、tanh、ReLU、ReLU变形和Maxout

Sigmoid函数 Sigmoid函数计算公式 sigmoid:x取值范围(-∞,∞),值域是(0, 1)。 sigmoid函数求导 这是sigmoid函数的一个重要性质。 图像 代码 # -*- coding: utf-8 -*- """ author: tom """import numpy im…

Python查看、修改pip install 默认使用的pip库

一、更改 Python 的 pip install 默认使用的pip库 【方法】只需要将对应python脚本的路径添加到系统环境变量Path中较前的位置即可,这样就可以获得优先级。 1、打开终端,可以通过指令 pip show pip 查看默认使用的pip包: 2、现在&#xff…

cmd 找到8080对应进程_多进程概括

多进程图像操作系统记录进程,并按照合理的次序交替推进(分配资源,不断调度),提高CPU利用率和程序执行速度,这就是操作系统的多进程图像。当操作系统启动时,多进程图像就出现了。 在linux内核源码main.c文件中&#xff…

Eclipse中安装Ext插件(Spket IDE)

在网上找了很多资料,这里重新整理一下。 Spket IDE是目前支持Ext 2.0最为出色的IDE。 它采用.jsb project file 文件并将继承于基类和所有文档的内容嵌入到生成代码提示的. doc中。由于Spket只是一个单纯的编辑器,没有其它格式的支持(如CSS&a…

python去噪音_python中的噪声是什么意思

你的序列均值为零吗? 方差随时间变化吗? 值与延迟值相关吗? 你可以用一些工具来检查你的时间序列是否为白噪音: 创建一个折线图。检查总体特征,如变化的平均值,方差或延迟变量之间的明显关系。 计算汇总统计。对照序列中有意义的连续块的均值和方差&a…

VC DLL学习

1 用VC创建DLL动态链接库1.1 创建dll项目1.2 为dll项目编写源文件头文件dllDemo.hextern"C"_declspec(dllexport) intSum(inta,intb);//加法函数。extern"C"_declspec(dllexport) intMax(inta, intb);//取较大值函数extern"C"_declspec(dllexpor…

乐在其中设计模式(C#) - 原型模式(Prototype Pattern)

[索引页][源码下载]乐在其中设计模式(C#) - 原型模式(Prototype Pattern)作者:webabcd介绍用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。示例有一个Message实体类,现在要克隆它。MessageModelusing System; using Syst…

java dump分析工具_Java 性能分析工具 (2):Java 内置监控工具

引言本文为 Java 性能分析工具系列文章第二篇,第一篇:操作系统工具。在本文中将介绍如何使用 Java 内置监控工具更加深入的了解 Java 应用程序和 JVM 本身。在 JDK 中有许多内置的工具,其中包括:jcmd:打印一个 Java 进…

Python面试题(第二篇)

第二部分 网络编程和并发(34题)1、简述 OSI 七层协议。2、什么是C/S和B/S架构?3、简述 三次握手、四次挥手的流程。4、什么是arp协议?5、TCP和UDP的区别?6、什么是局域网和广域网?7、为何基于tcp协议的通信…

noip模拟赛 radius

分析:这道题实在是不好想,一个可以骗分的想法是假定要求的那个点在中心点上,可以骗得不少分.但是在边上的点要怎么确定呢?理论复杂度O(﹢无穷).答案一定是和端点有关的,涉及到最大值最小,考虑二分最大值&…

c语言中int的取值范围_c语言入门(1)

c语言入门C语言一经出现就以其功能丰富、表达能力强、灵活方便、应用面广等特点迅速在全世界普及和推广。C语言不但执行效率高而且可移植性好,可以用来开发应用软件、驱动、操作系统等。C语言也是其它众多高级语言的鼻祖语言,所以说学习C语言是进入编程世…

vue : 无法将“vue”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确, 然后再试一次。

vue : 无法将“vue”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确, 然后再试一次。 报错原因: 没有安装脚手架vue-cli 解决方法:安装脚手架vue-cli npm inst…

session的生命周期

session的生命周期分为创建、活动、销毁三个阶段 创建一个新的会话不代表旧的会话就销毁了 session.invalidate()方法可以销毁当前会话 在page1中写上这个方法再打开网页 说明该会话被销毁了出现了错误 我们把这个方法写在所有代码段的下面 再打开这个网页 刷新网页 这个sessio…

虚拟化与网络

本文转自Grodd51CTO博客,原文链接:http://blog.51cto.com/juispan/1959791,如需转载请自行联系原作者

算法基础之搜索和经典排序

目录 简介 搜索算法 二分法查找 排序算法 冒泡排序(Bubble Sort) 选择排序(Selection Sort) 插入排序(Insert Sort) 快速排序(Quick Sort) 归并排序(Merge Sort…

背景宽高随文本变化_中科大提出ContourNet:更准确的任意形状场景文本检测新方法...

点击上方“CVer”,选择加"星标"置顶重磅干货,第一时间送达本文转载自:CSIG文档图像分析与识别专委会本文简要介绍2020年被CVPR录用的论文“ContourNet: Taking a Further Step toward Accurate Arbitrary-shaped Scene Text Detect…

算法面试题汇总(更新中)

1、根据数字返回相应位置数字 def get_digit(num, i):# i0 个位 1 十位 2 百位...return num // (10 ** i) % 10# print(get_digit(12345, 6)) 2、列表反转,不用内置函数 def reverse_list(li):n len(li)for i in range(n // 2):li[i], li[n-i-1] li[n-i-1], …

使用Jmeter对mysql进行性能测试入门

使用Jmeter对mysql进行性能测试入门 第一步:测试环境准备: 1)、mysql> select version(); ----------- | version() | ----------- | 5.5.13 | ----------- ms数据库数据: mysql> select count(*) from account; ----------…

算法基础之数据结构

whats the 数据结构 数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成。 简单来说,数据结构就是设计数据以何种方式组织并存储在计算机中。 比如:列表、集合与字典等都是一种数据结构。 通常情况下&#xff…

soap接口怎么不返回tuple python_Python 中的接口

Python 是动态类型语言, 只在运行时做 Duck Typing 检查.利: 灵活, 方便弊: 代码混乱, 缺少规范标准自带两类接口支持: abc 和 typing.Protocol, 有他们协助给天马行空的程序员套上枷锁, Python 的大工程才可以"上道"abcabc 就是 Abstract Base Class, 虚基类. 跟 Ja…