Java内置锁——synchronized

一、给对象加把锁

synchronized关键字是Java唯一内置的互斥锁,通过关键字 synchronized 可以保证同一时刻只有一个线程获得某个同步代码块的执行权,但不会导致其他线程执行非同步方法时阻塞。

当获得锁的线程执行完同步代码块后,线程会将锁释放,其他由于锁占用导致阻塞的线程可以通过非公平的方式(非公平指的是获得锁的操作不是按照请求锁的顺序,即没有先来后到之分)获得锁,并进入同步代码块执行代码。

虽然我们通常要在方法上或是为某个代码块标记 synchronized,但实际上,JVM实现锁的逻辑是通过给对象加锁。

如果将一个成员方法标记为synchronized,那么执行这个方法时就需要给当前对象 this 加锁。对象只有一个,而synchronized方法可能有多个,因此,如果某个线程已经获得了这把锁,那么就意味着其他线程不仅没有权限执行当前方法,就连其他同步方法也无法执行,因为这些同步方法都需要获得同一个对象锁。

想要证明是锁对象其实非常简单,下面这个demo可以看出synchronized锁定的是同一个共享对象,而不是这个对象的某一个加锁方法。

import java.util.concurrent.TimeUnit;/*** 此例证明了synchronized锁是锁定的对象而不是代码片段 <br>* 类名:ObjectLockDemo<br>* 作者: mht<br>* 日期: 2018年8月27日-下午1:25:47<br>*/
public class ObjectLockDemo {public static void main(String[] args) {T t = new T();new Thread(() -> t.doSomthing(), "t1").start();new Thread(() -> t.doOtherthing(), "t2").start();}
}class T {synchronized void doSomthing() {System.out.println(Thread.currentThread().getName() + " do something start...");try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " do something end...");}synchronized void doOtherthing() {System.out.println(Thread.currentThread().getName() + " do otherthing...start");System.out.println(Thread.currentThread().getName() + " do otherthing...end");}
}

上面这个例子非常简单。首先,我们为T类创建了两个加锁的方法doSomthing()doOtherthing(),在主线程中,我们创建了一个 t 对象,然后通过线程 t1 调用 对象 t doSomthing()加锁方法,紧接着线程 t2 去调用 对象 t 的另一个加锁方法doOtherthing()如果synchronized锁住的是方法,那么在 t1 执行的5秒内,t2 可以自由的执行doOtherthing()方法而不受限制,但是如果是锁住了 对象那么线程 t2 只有当 t1 执行完成后释放了对象锁,才能去执行 doOtherthing()方法,也就证明了synchronized锁住的是 t 对象。

执行结果如下,很明显,t2 等到 t1 完成后才执行了线程,synchronized锁住的是对象。

二、synchronized用法

synchronized不论如何使用,都是直接锁住了对象,但是在写法上,却存在很多变式:

用法一:

public class A {private Object o = new Object();private int count = 10;public void m() {synchronized (o) {// 任何线程若想执行代码块中的语句,必须先拿到o的锁count--;System.out.println(Thread.currentThread().getName() + " count = " + count);}}
}

用法二:

public class A {private int count = 10;public void m() {synchronized (this) {// 任何线程若想执行代码块中的语句,必须先拿到this的锁count--;System.out.println(Thread.currentThread().getName() + " count = " + count);}}
}

synchronized锁定的是一个对象,任何代表对象的事物都可以作为synchronized参数。

用法三:

public class A {private int count = 10;public synchronized void m() { // 等同于在方法的代码执行时要synchronized(this)count--;System.out.println(Thread.currentThread().getName() + " count = " + count);}
}

上面代码就是平时比较常见的加锁方式,虽然synchronized修饰了方法,但是一定不能理解为锁定了代码块,而是执行这段代码的当前对象 this

用法四(修饰静态方法):

public class A {private static int count = 10;public synchronized static void m() { // 这里相当于synchronized(thingking.com.A.class)count--;System.out.println(Thread.currentThread().getName() + " count = " + count);}public static void mm() {synchronized (A.class) {// 这是不可以写this,this指代对象,而静态方法是通过类来调用的,没有this对象count--;}}
}

三、互斥锁的重入性

作为一种互斥锁,synchronized可以避免并发情景下的线程乱入问题,保证了同步代码块的顺序执行。

重入性指的是,允许同一个线程重复获得已经获得的锁,不会造成阻塞,即重复进入同步代码块。重入性是互斥锁的一个重要特性,如果在同步代码块中又调用了该对象的其他同步方法,那么就会出现当前线程需要反复获取锁的情况,如果锁不允许同一个线程重入,就会出现“我拿不到我自己占用的锁”的尴尬情况,最终导致死锁!

public class T {synchronized void m() {System.out.println("m start...");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("m end...");}public static void main(String[] args) {new TT().m();}
}class TT extends T {@Overridesynchronized void m() {System.out.println("child m start...");super.m();System.out.println("child m end...");}
}

上述代码是重入性的一种典型应用,子类中的同步方法调用父类中的同步方法,也是允许重入的。当调用子类的同步方法时,实际上锁定的是this,而子类的对象同样也可以看做是一个父类对象,因此这个锁是允许重入的,不会造成死锁。

执行结果:

四、注意异常

在默认情况下,synchronized锁在发生异常时会自动释放锁。所以在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况。

比如,在一个web app处理过程中,多个servlet 线程共同访问同一个资源,这是如果如果异常处理不合适,在第一个线程中抛出异常,其他线程会进入同步代码块,有可能会访问到异常产生的数据,因此要非常小心的处理同步业务逻辑中的异常。

public class T {int count = 0;synchronized void m() {System.out.println(Thread.currentThread().getName() + " start...");while (true) {count++;System.out.println(Thread.currentThread().getName() + " count = " + count);try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}if (count == 5) {int i = 1 / 0;// 此处抛出异常,锁将被释放,要想不释放,可以在这里进行catch,然后让循环继续}}}public static void main(String[] args) {T t = new T();Runnable r = new Runnable() {@Overridepublic void run() {t.m();}};new Thread(r, "t1").start();try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}new Thread(r, "t2").start();}
}

执行结果:

五、锁的优化与升级

5.1 synchronized实现原理

Java 语言中为每个引用对象实例都设置了一把锁,JVM基于进入和退出 monitor 对象来实现同步的。

monitor对象是与对象实例相关联的一个锁对象。

当代码块被synchronized修饰后,编译器会在同步代码块开始的位置插入一条 monitorenter 指令,代表进入monitor 对象;同时会在同步块结束的位置插入一条monitorexit指令,代表退出 monitor 对象。

JVM保证每个monitorenter都有一个monitorexit 与之匹配。任何对象都有一个monitor与之关联,并且一个monitor被持有后,它将处于锁定状态。

5.2 偏向锁

在synchronized设计之初,都是通过上面的逻辑来实现同步代码的,这也导致了性能上被很多人诟病。

随着研究的深入,人们发现,在很多场景下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得

为了避免这种情况下去加锁,JVM会在线程第一次获得锁的时候,在对象头上标记线程id以及锁状态,在下次请求锁的时候,偏向于当前线程。

5.3 自旋锁

当存在两条以上的线程请求锁时,线程会尝试通过循环来尝试请求锁,旧的虚拟机实现上是循环10次,现在的JVM会更加智能地判断循环次数。

总之,当线程需要获得锁而锁已经被占用的情况下,线程就会自旋等待,由于自旋本质上就是 while循环,是一种线程运行状态,因此会消耗CPU资源。

如果同步代码块很小,竞争锁的线程个数不多,那么自旋锁在一定程度上也可以实现重量级锁的优化。

5.4 重量级锁

重量级锁是即synchronized的最终实现,它需要请求操作系统为对象上锁,保证同步代码块的有序执行。

锁升级的过程是 无锁-->偏向锁-->自旋锁-->重量级锁,随着并发增大,同步代码块的执行时间变长,锁的“重量”会逐步升级,且不可逆。

 

 

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

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

相关文章

处理对象(toString()方法详解和==与equals方法的区别)

处理对象&#xff08;toString&#xff08;&#xff09;方法详解和与equals方法的区别&#xff09;toString&#xff08;&#xff09;是一个非常特殊的方法&#xff0c;它是一个自我描述的方法。当程序员直接打印该对象的时候&#xff0c;系统会输出该对象的“自我描述”的信息…

Java并发编程实战——volatile

引言 Java 语言提供了一种弱同步机制——volatile 变量。它的作用是确保将变量的更新操作通知到其他线程。 当把变量声明为volatile后&#xff0c;编译器和运行时都会注意到这个变量是共享的&#xff0c;因此不会将该变量上的操作与其他内存操作一起重排序。 另外&#xff0…

Java中类的加载顺序介绍(ClassLoader)

Java中类的加载顺序介绍(ClassLoader)1、ClassNotFoundExcetpion   我们在开发中&#xff0c;经常可以遇见java.lang.ClassNotFoundExcetpion这个异常&#xff0c;今天我就来总结一下这个问题。对于这个异常&#xff0c;它实质涉及到了java技术体系中的类加载。Java的类加载机…

UP装机部署步骤大纲

Linux装机 插上网线&#xff0c;然后&#xff0c;Ubuntu系统安装&#xff08;略&#xff09; 更改root密码 以装机时设置的用户登陆后&#xff0c;sudo修改root用户密码&#xff0c;然后退出&#xff0c;重新登录root。 $ sudo passwd root 下载并安装JDK $ java (根据提…

Java 多线程 —— wait 与 notify

引言 认识一下 Object 类中的两个和多线程有关的方法&#xff1a;wait 和 notify。 wait&#xff0c;当前线程进入 WAITING 状态&#xff0c;释放锁资源。 notify&#xff0c;唤醒等待中的线程&#xff0c;不释放锁资源。 一、使用 wait-notify 实现一个监控程序 实现一个…

重写equal()时为什么也得重写hashCode()之深度解读equal方法与hashCode方法渊源

重写equal()时为什么也得重写hashCode()之深度解读以及equal方法与hashCode方法渊源转载自&#xff1a;http://blog.csdn.net/javazejian/article/details/51348320 今天这篇文章我们打算来深度解读一下equal方法以及其关联方法hashCode()&#xff0c;我们准备从以下几点入手分…

Java8————Optional

引言 Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true&#xff0c;调用get()方法会返回该对象。 Optional 是个容器&#xff1a;它可以保存类型T的值&#xff0c;或者仅仅保存null。Optional提供很多有用的方法&#xff0c;这样我们就不用显式进…

问题反馈信息处理平台开发过程

问题反馈信息处理平台开发过程 “问题反馈信息处理平台”是一个将用户反馈上来的出错信息进行处理和收集的一个平台。 这个项目主要都是在实习的时候由我一个人进行开发&#xff0c;我导师在旁边进行指导完成的。 该项目的技术架构主要是&#xff1a; 前端主要基于Vue框架的…

Java8————Base64

Base64&#xff1f; Base64是一种用64个字符来表示任意二进制数据的方式。 对于二进制文件如图片、exe、音频、视频等&#xff0c;包含很多无法显示和打印的字符&#xff0c;如果希望能够通过记事本这样的文本处理软件处理二进制数据&#xff0c;就需要一个二进制转字符串的转…

大众点评后端项目解析

restful Api是一种设计风格&#xff1a; 启动前端项目&#xff1a;在前端项目根目录 npm install&#xff1a;加载依赖包 npm run mock&#xff1a;提供模拟数据的接口&#xff0c;前端脱离于后台&#xff1b;start /b npm run mock&#xff08;于后台运行&#xff09; npm…

Java中类及方法的加载顺序

代码展示 请运行下面代码&#xff0c;查看运行结果&#xff0c;并带着问题&#xff0c;尝试第二次debug程序。 class A {private static int numA;private int numA2;static {System.out.println("A的静态字段 : " numA);System.out.println("A的静态代码块…

新手入门教程-------Spring Boot中集成RabbitMQ

AMQP&#xff1a;是Advanced Message Queuing Protocol的简称&#xff0c;高级消息队列协议&#xff0c;是一个面向消息中间件的开放式标准应用层协议。 定义了以下特性&#xff1a; 消息方向消息队列消息路由&#xff08;包括&#xff1a;点到点和发布-订阅模式&#xff09;可…

Java 多线程 —— ReentrantLock 与 Condition

引言 ReentrantLock 是 JUC 下的一个功能强劲的锁工具&#xff0c;支持公平锁、非公平锁&#xff0c;以及多等待队列的 Condition 。 也常常被称为“手动锁”。本篇博客主要分析它的使用方法以及 Condition 实现的一个生产者消费者模式。 一、可替代 synchronized 的手动锁 …

Rabbitmq+Springboot设计秒杀应用

秒杀业务的核心是库存处理&#xff0c;用户购买成功后会进行减库存操作&#xff0c;并记录购买明细。当秒杀开始时&#xff0c;大量用户同时发起请求&#xff0c;这是一个并行操作&#xff0c;多条更新库存数量的SQL语句会同时竞争秒杀商品所处数据库表里的那行数据&#xff0c…

Java8————Stream API

引言 Java8 加入了java.util.stream包&#xff0c;这个包中的相关API将极大的增强容器对象对元素的操作能力。 它专注于对集合对象进行各种便利、高效的聚合操作&#xff0c;或大批量数据处理。 Stream API借助于同样新出现的Lambda表达式&#xff0c;极大的提高了编程效率和…

MySQL数据库知识点总结

数据库&#xff1a; 数据库索引的好处&#xff1a;索引是对数据库表中的一个或多个列的值进行排序的结构&#xff0c;这样检索或者查询某条记录的时候就不在是顺序查找&#xff0c;而是使用特定的查找方式进行查找&#xff0c;如通过二分查找或者是hash值来查找&#xff0c;提高…

Java8 函数式对齐约定————Eclipse自定义代码风格

引言 Java8 的函数式代码风格在Stream的使用上尤为突出。尽管我们可以通过连续调用函数完成一系列操作&#xff0c;但是其可读性并不能保证&#xff0c;还需要有与之相辅的Code Style。例如&#xff0c;请尝试阅读下面两段完全相同的代码&#xff1a; 未遵守约定格式&#xf…

Java核心篇之JVM--day3

Java核心篇之JVM--day3 Java JVM详解--通俗易懂教程 JVM&#xff1a;Java虚拟机的简称。 谈到JVM&#xff0c;通常会聊到三个问题&#xff1a; 1. 什么时候触发Java GC&#xff1f; 2. 对什么东西进行Java GC&#xff1f; 3. 如何进行Java GC&#xff1f; 首先解决第…

使用springboot来实现WebLog

使用websocket技术实时输出系统日志到浏览器端&#xff0c;实现WebLog boot-websocket-log&#xff1a; spring boot系统中使用websocket技术实时输出系统日志到浏览器端&#xff0c;因为是实时输出&#xff0c;所有第一时间就想到了使用webSocket,而且在spring boot中&#…

设计模式---观察者模式介绍与理解

设计模式---观察者模式介绍与理解&#xff1a; 观察者模式原理&#xff1a;类似于定牛奶业务 1. 奶站&#xff0c;subject&#xff1a;登记注册&#xff0c;移除&#xff0c;通知&#xff08;register&#xff0c;remove&#xff0c;notify&#xff09; 2. 用户&#xff0c;…