【Java】synchronized关键字笔记

Java Synchronized 关键字

壹. Java并发编程存在的问题

1. 可见性问题

可见性问题是指一个线程不能立刻拿到另外一个线程对共享变量的修改的结果。

如:

package Note.concurrency;public class Demo07 {private static boolean s = true;public static void main(String[] args) {new Thread(() -> {while(s) {}}).start();try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {s = false;}).start();System.out.println(s);}
}

运行之后,第一个线程一直没有停止,说明第二个线程对s的修改没有立刻被线程1拿到

2. 原子性问题

原子性问题是指一条Java语句有可能会被编译成多条语句执行,多线程环境下修改同一个变量就会导致结果错误,如:

package Note.concurrency;import java.util.ArrayList;
import java.util.List;public class Demo08 {private static int num = 0;public static void main(String[] args) {Runnable runnable = () -> {num ++;};List<Thread> list = new ArrayList<Thread>();for (int i = 0; i < 5; i++) {Thread thread = new Thread(runnable);thread.start();list.add(thread);}for (Thread t :list) {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(num);}
}//~ 4

由于++不是原子性操作,通过反编译,一个自增语句会被翻译成四条语句执行:

private static void lambda$main$0();descriptor: ()Vflags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETICCode:stack=2, locals=0, args_size=00: getstatic     #16                 // Field num:I3: iconst_14: iadd5: putstatic     #16                 // Field num:I8: returnLineNumberTable:line 10: 0line 11: 8

3. 有序性问题

Java编译时会优化代码,这时如果两个无关联的语句Java可能会调整他的顺序,导致有序性问题。

贰. 线程状态

UTOOLS1586703796900.png

UTOOLS1586591638004.png

叁. Synchronized的用法

1. 修饰成员方法

在定义成员方法时添加关键字Synchronized可以保证同时只有一个线程执行此成员方法,线程进入成员方法时,需要先获取锁,方法执行完毕后,会自动释放锁,Synchronized修饰成员方法时,使用的锁对象是this

2. 修饰静态方法

因为静态方法所有实例共享一份,所以相当于给类加锁,锁对象默认是当前类的字节码文件,所以用Synchronized修饰的成员方法和静态方法是可以并发运行的。

例:双重检验锁实现线程安全的单例模式:

package Note.concurrency;public class Singleton {private volatile static Singleton singleton;private Singleton(){}public static Singleton getSingleton() {if (singleton == null) {synchronized (Singleton.class) {if(singleton == null)singleton = new Singleton();}}return singleton;}
}
  • singleton = new Singleton();不是原子操作,会被翻译成下面四句之类,为保证线程安全,需要放在synchronized代码块中。
17: new           #3                  // class Note/concurrency/Singleton
20: dup
21: invokespecial #4                  // Method "<init>":()V
24: putstatic     #2                  // Field 
  • 为了避免不可见性问题,共享变量使用Volatile关键字修饰

3. 修饰代码块

修饰代码块时,要指定锁对象,可以是任意的Object对象(尽量不要是String xxx , 因为String池有缓存),每个线程需要执行Synchronized代码块中的代码时,要先获取锁对象,否则就会被阻塞, 代码块结束,锁对象自动释放。

肆. Synchronized的特性

1. 可重入

Synchronized是一个可重入锁,意思是在获得锁后可以再次获得该锁而不会陷入死锁

package Note.concurrency;public class ReentrantLockDemo {private int num = 0;final Object object = new Object();private void method1() {synchronized (object) {num ++;}}private void method2() {synchronized (object) {method1();}}public static void main(String[] args) {ReentrantLockDemo reentrantLockDemo = new ReentrantLockDemo();new Thread(reentrantLockDemo::method2).start();new Thread(reentrantLockDemo::method2).start();}
}

2. 不可中断

如果有A,B两个线程竞争锁,如果使用Synchronized,A获得锁后,如果不释放,B将一直等下去,不能中断。

package Note.concurrency;public class UninterruptibleDemo {private static final Object o = new Object();public static void main(String[] args) throws InterruptedException {// 线程1 拿到锁后阻塞3snew Thread(() -> {synchronized (o) {System.out.println("线程1的同步代码块开始执行");try {Thread.sleep(6000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(" 线程1的同步代码块执行结束");}}).start();// 让线程1先执行Thread.sleep(100);Thread t2 = new Thread(() -> {synchronized (o) {System.out.println("线程2的同步代码块开始执行");}});t2.start();// 主线程休眠3s后,锁o 依然被线程1拿着,线程2处于BLOCKED状态Thread.sleep(3000);System.out.println("线程2的状态:" + t2.getState());// 尝试中断线程2,如果synchronized允许被中断,那线程2此时的状态应该会变为Terminated(死亡)状态// 反之synchronized如果不可中断,线程2的状态会保持BLOCKED(阻塞)状态t2.interrupt();System.out.println("线程2的状态:" + t2.getState());}
}//线程1的同步代码块开始执行
//线程2的状态:BLOCKED
//线程2的状态:BLOCKED
//线程1的同步代码块执行结束
//线程2的同步代码块开始执行

伍. Synchronized的原理

通过javap反汇编一下代码

package Note.concurrency;public class Demo10 {public static synchronized void testMethod() {}public static void main(String[] args) {testMethod();synchronized(Demo10.class) {System.out.println("");}}
}

可以看到,通过Synchronized修饰的代码块:

 public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=3, args_size=10: ldc           #2                  // class Note/concurrency/Demo102: dup3: astore_14: monitorenter5: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;8: ldc           #4                  // String10: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V13: aload_114: monitorexit15: goto          2318: astore_219: aload_120: monitorexit21: aload_222: athrow23: return
Exception table:from    to  target type5    15    18   any18    21    18   any

对比普通语句,多了monitorentermonitorexit,monitorenter是同步代码块开始的地方,monitorexit是同步代码块结束的地方,当线程运行到monitorenter后,会试图获取monitor对象,这个对象定义在JVM层(是一个C++的对象),每个Java对象都可以和一个monitor关联,这个monitor对象保存在Java对象的对象头中(这也是为什么任意的object对象都可以作为锁的原因),这个monitor对象中有一个recursions属性,用来保存被锁的次数,第一次运行到monitorenter时,检查要获取的锁关联的monitor对象的recursions是不是0,如果是0,说明该锁没有被任何人获取,就可以获取锁,把锁的recursions加一,并将monitor对象的owner属性设置为当前的Java对象。当执行monitorexit时, 会将recursions减一,当recursions减为0时,标志着当前占有锁的线程释放锁。

第20行还有一个monitorexit,最下面的异常表显示,如果5到15行发生异常,从18行开始执行,说明如果同步代码块中发生异常, 锁会被自动释放。

synchronized修饰方法的情况

  public static synchronized void testMethod();descriptor: ()Vflags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZEDCode:stack=0, locals=0, args_size=00: returnLineNumberTable:line 5: 0

如果使用synchronized修饰方法,该方法会被添加上ACC_SYNCHRONIZED标记,添加了该标记的方法在执行时会隐式的调用monitorentermonitorexit

陆. Synchronized和ReentrantLock的区别

  1. Synchronized是一个关键字,依赖于JVM,而ReentrantLock是一个类,依赖于API;
  2. Synchronized可以修饰方法,ReentrantLock只能修饰代码块;
  3. synchronized可以自动释放锁,哪怕被它修饰的方法或代码块发生异常,它也可以把锁释放了,但ReentrantLock需要手动调用unlock()释放锁,与try...finally配合使用,避免死锁;
  4. ReentrantLock有比Synchronized更丰富的功能,如:
    • ReentrantLock可以做公平锁,也可以做非公平锁,但Synchronized就是非公平锁。
    • ReentrantLock可以判断对象有没有拿到锁。
    • ReentrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()等待锁的线程可以放弃等待锁去干别的事情。
    • Lock可以通过使用读锁提高性能。

柒. Java 6 对Synchronized的优化

1. 偏向锁

大多数情况下,锁总是右同一线程多次获得,不存在线程竞争,所以偏向锁就是适用于这种情况,当有线程第一次获取锁时,JVM会把对象头中的标志位设置为01,即偏向模式,并且将线程ID记录下来,以后线程每次访问同步代码块只需要判断线程ID是不是记录的偏向线程的ID,如果是就直接不进行同步了。但如果一旦发生线程竞争,偏向锁就会升级为轻量级锁。由于偏向锁只能在全局安全点(所有线程全部停止)撤销,所以在存在线程竞争的环境下使用偏向锁会得不偿失。

在JDK5中偏向锁默认是关闭的,而到了JDK6中偏向锁已经默认开启。但在应用程序启动几秒钟之后才 激活,可以使用 -XX:BiasedLockingStartupDelay=0 参数关闭延迟,如果确定应用程序中所有锁通常 情况下处于竞争状态,可以通过 XX:-UseBiasedLocking=false 参数关闭偏向锁。

2. 轻量级锁

偏向锁失效后会升级为轻量级锁,轻量级锁适用于线程交替执行同步代码块的情况下,它使用CAS操作代替重量级锁使用操作系统互斥变量的操作,因此避免了程序频繁在系统态和用户态之间切换的开销,但之所以使用轻量级锁,是基于“对于绝大部分锁,在整个同步周期内都是不存在竞争的”的经验数据,如果有多个线程同时进入临界区,那CAS操作的效率可能反而不如重量级锁,如果存在线程竞争,轻量级锁就会膨胀为重量级锁。

3. 自旋锁

一般情况下,同步代码块中的代码执行时间都比较短,所以一时间获取不到锁,可能再重试一次就可以了,而不用升级为重量级锁,自旋锁就是基于这个原理,它允许获取不到锁的线程重复几次尝试获取锁,默认是10次,JDK 1.6 开始加入自适应自旋锁,会根据之前自旋的情况动态确定自旋的次数。

4. 重量级锁

经过自旋后还是获取不到锁,那就会升级为重量级锁,也就是monitor

5. 锁消除

锁消除理解起来很简单,它指的就是虚拟机即使编译器在运行时,如果检测到那些共享数据不可能存在竞争,那么就执行锁消除。锁消除可以节省毫无意义的请求锁的时间。

5. 锁粗化

JVM会探测到一连串细小的操作都使用同一个对象加锁,将同步代码块的范围放大,放 到这串操作的外面,这样只需要加一次锁即可。

捌. 使用Synchronized时的优化

  1. 减少Synchronized的范围,让同步代码块中的代码执行时间尽可能短
  2. 降低锁粒度,将一个大锁改为多个不同锁对象的小锁,如HashTable和ConcurrentHashMap
  3. 读写分离,读不加锁,写加锁。

拾. 参考

https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/Multithread/synchronized.md

https://www.bilibili.com/video/BV1aJ411V763

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

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

相关文章

SQL Server-数据类型(七)

前言 前面几篇文章我们讲解了索引有关知识&#xff0c;这一节我们再继续我们下面内容讲解&#xff0c;简短的内容&#xff0c;深入的理解&#xff0c;Always to review the basics。 数据类型 SQL Server支持两种字符数据类型&#xff0c;一种是常规&#xff0c;另外一种则是Un…

pb retrieve时停止工作_大佬们挂在嘴边的PE、PB是什么?

在紧锣密鼓地准备科创50ETF的发行工作间隙&#xff0c;今天小夏先带你读懂最简单的PE、PB估值指标这两大指标。01、什么是PE&#xff08;市盈率&#xff09;PE&#xff0c;也就是市价盈利比率&#xff0c;简称市盈率。市盈率是指股票价格与每股收益&#xff08;每股收益&#x…

【设计模式 01】简单工厂模式(Simple factory pattern)

简单工厂模式 可以根据参数的不同返回不同类的实例 参考&#xff1a; CSDN|简单工厂模式 简单工厂通过传给工厂类的参数的不同&#xff0c;返回不同的对象&#xff0c;包括三部分组成&#xff1a; 具体的”产品“工厂类&#xff08;实例化并返回”产品“&#xff09;客户端&am…

用Visual Studio 2019连接 WSL来编译调试C/C++项目

因为有作业要在Linux环境下写&#xff0c;用虚拟机直接卡成PPT&#xff0c;VS code又不会调试&#xff0c;就搞一下VS 2019吧。 环境 windows 10 WSL(Ubuntu 18.04.4) Visual Studio Community 2019 Linux 里要有C/C环境&#xff08;gcc等&#xff09;VS要有 适用于 Linux…

系统移植的四大步骤

最近在学习系统移植的相关知识&#xff0c;在学习和调试过程中&#xff0c;发现了很多问题&#xff0c;也解决了很多问题&#xff0c;但总是对于我们的开发结果有一种莫名其妙的感觉&#xff0c;纠其原因&#xff0c;主要对于我们的开发环境没有一个深刻的认识&#xff0c;有时…

display:flex

flex&#xff1a;弹性布局 常用属性介绍&#xff1a; flex-direction: column &#xff08;设置主容器主轴方向&#xff09; flex-flow: row wrap &#xff08;第一个参数为flex-direction&#xff0c;第二个为flex-wrap&#xff09; align-items: flex-start &#xff08;设…

Linux(Ubuntu 19.10)下 Qt5 连接 MySQL(QMYSQL driver not loaded)

Linux&#xff08;Ubuntu 19.10&#xff09;下 Qt5 连接 MySQL 安装好 MySQL 和 Qt Qt 连接 MySQL 的代码 QSqlDatabase dQSqlDatabase::addDatabase("QMYSQL");//加载mysql驱动&#xff0c;这个字符串是固定的 d.setHostName("127.0.0.1"); d.setDatabas…

Wpf 数据绑定简介、实例1

简介&#xff1a;1.WPF绑定使用的源属性必须是依赖项属性&#xff0c;这是因为依赖项属性具有内置的更改通知支持&#xff0c;元素绑定表达式使用了Xaml扩展标记&#xff0c; WPF绑定一个控件是使用Binding.ElementName, 绑定非控件对象时使用Source,RelativeSource,DataContex…

【设计模式 04】代理模式

代理模式 代理模式( Proxy)&#xff1a;为其他对象提供一种代理以控制对这个对象的访问。 参考&#xff1a;refactoringguru | proxy 什么是代理模式 有时候如果想要访问某个对象&#xff0c;但又没办法直接访问或不方便直接访问&#xff0c;可以使用代理模式&#xff0c;代理…

css 大于号 标签_CSS设计基础选择器篇

点击上方 Java项目学习 &#xff0c;选择 星标 公众号重磅资讯、干货&#xff0c;第一时间送达前言&#xff1a;如果将CSS样式应用于特定的网页对象上&#xff0c;需要先找到目标元素。在CSS样式中执行这一任务的部分被称为选择器。1 标签选择器优点&#xff1a;为页面中同类型…

crontab 提示 command not found 解决方案

crontab 提示 command not found 解决方案 今天遇见一个问题&#xff0c;crontab的定时任务会报错&#xff1a;java command not found&#xff0c;但是手动执行脚本一直能成功。 猜想是环境变量的问题。 在crontab里添加个打印环境变量的任务&#xff1a; * * * * * echo $PAT…

【设计模式 05】工厂方法模式

工厂方法模式 define an interface or abstract class for creating an object but let the subclasses decide which class to instantiate. 参考&#xff1a; refactoringguru | factory-methodjavatpoint | factory-method-design-pattern博客园| 工厂方法 简单工厂的问题 …

LinkedList类源码浅析(二)

1、上一节介绍了LinkedList的几个基本的方法&#xff0c;其他方法类似&#xff0c;就不一一介绍&#xff1b; 现在再来看一个删除的方法&#xff1a;remove(Object o) remove方法接受一个Object参数&#xff0c;这里需要对参数做空与非空处理&#xff1b; 但是删除一个Object元…

2016OSC源创会年终盛典-综合技术专场-张小刚

2019独角兽企业重金招聘Python工程师标准>>> 综合技术专场 讲师/SPEAKERS 张小刚 网易云负载均衡项目负责人 《网易蜂巢负载均衡技术实践》从网易蜂巢中的实践出发&#xff0c;分享网易蜂巢负载均衡服务从无到有&#xff0c;从私有云到公有云过程中的技术实践。重点…

mysql 日期

数据类型 数据类型格式date YYYY-MM-DD datetime YYYY-MM-DD HH:MM:SS timestamp YYYY-MM-DD HH:MM:SS year YYYY 或 YY 具体实现的函数 1、now() 返回当前的日期和时间 SELECT NOW(); 2、curdate() 返回当前的日期 SELECT CURdate(); 3、curtime&#xff08;&#xff09;返回当…

超完整的 Chrome 浏览器客户端调试大全

2019独角兽企业重金招聘Python工程师标准>>> 引言 “工欲善其事&#xff0c;必先利其器” 没错&#xff0c;这句话个人觉得说的特别有道理&#xff0c;举个例子来说吧&#xff0c;厉害的化妆师都有一套非常专业的刷子&#xff0c;散粉刷负责定妆&#xff0c;眼影刷负…

MySQL 为什么用索引,为什么是 B+树,怎么用索引

MySQL 索引 A database index is a data structure that improves the speed of operations in a table. Indexes can be created using one or more columns, providing the basis for both rapid random lookups and efficient ordering of access to records. 为什么需要索…

Servlet 生命周期、工作原理

Servlet 生命周期&#xff1a;Servlet 加载--->实例化--->服务--->销毁。init&#xff08;&#xff09;&#xff1a;在Servlet的生命周期中&#xff0c;仅执行一次init()方法。它是在服务器装入Servlet时执行的&#xff0c;负责初始化Servlet对象。可以配置服务器&…

【Go 并发控制】上下文 context 源码

Context 在 Go 服务中&#xff0c;往往由一个独立的 goroutine 去处理一次请求&#xff0c;但在这个 goroutine 中&#xff0c;可能会开启别的 goroutine 去执行一些具体的事务&#xff0c;如数据库&#xff0c;RPC 等&#xff0c;同时&#xff0c;这一组 goroutine 可能还需要…

iOS开发UI篇—模仿ipad版QQ空间登录界面

一、实现和步骤 1.一般ipad项目在命名的时候可以加一个HD,标明为高清版 2.设置项目的文件结构&#xff0c;分为home和login两个部分 3.登陆界面的设置 &#xff08;1&#xff09;设置第一个控制器和自定义的控制器类&#xff08;登陆&#xff09;关联 &#xff08;2&#xff09…