【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语句分析是否走索引_MySql 的SQL执行计划查看,判断是否走索引

在select窗口中&#xff0c;执行以下语句&#xff1a;set profiling 1; -- 打开profile分析工具show variables like %profil%; -- 查看是否生效show processlist; -- 查看进程use cmc; -- 选择数据库show PROFILE all; -- 全部分析的类型show index from t_log_account; ##查看…

SQL Server-数据类型(七)

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

【随记】SQL Server连接字符串参数说明

废话不多说&#xff0c;请参见 SqlConnection.ConnectionString 。 转载于:https://www.cnblogs.com/xiesong/p/5749037.html

【设计模式 00】设计模式的六大原则

设计模式的六大原则 参考&#xff1a; 设计模式六大原则 1. 单一职责原则 一个类只负责一个明确的功能 优点&#xff1a; 降低类的复杂度&#xff0c;提高代码可读性和可维护性降低变更时对其他功能的影响 2. 里氏替换原则 **原则一&#xff1a;**若 o1 是 C1 的一个实例化…

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

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

EF CodeFirst 如何通过配置自动创建数据库当模型改变时

最近悟出来一个道理&#xff0c;在这儿分享给大家&#xff1a;学历代表你的过去&#xff0c;能力代表你的现在&#xff0c;学习代表你的将来。 十年河东十年河西&#xff0c;莫欺少年穷 学无止境&#xff0c;精益求精 本篇为进阶篇&#xff0c;也是弥补自己之前没搞明白的地方,…

对AutoIt中控件和窗口的理解

经过尝试&#xff0c;对AutoIt中Control和Window有了新的认识&#xff0c;分享一下 1.Control 现在我想对一个WinForm架构的应用程序进行自动化操作&#xff0c;得到控件Advanced Mode属性为[Name:XXX]。 然而在该窗口中有多个相同属性的Control&#xff0c;而依该属性只能操作…

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

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

[Hadoop]MapReduce多路径输入与多个输入

1. 多路径输入 FileInputFormat是所有使用文件作为其数据源的 InputFormat 实现的基类&#xff0c;它的主要作用是指出作业的输入文件位置。因为作业的输入被设定为一组路径&#xff0c; 这对指定作业输入提供了很强的灵活性。FileInputFormat 提供了四种静态方法来设定 Job 的…

pvrect r语言 聚类_R语言实现KEGG通路富集可视化

用过KEGG的朋友应该都很熟悉里面的通路地图。你是否想过如果自己可以控制通路图将自己的基因绘制在一个通路图中&#xff0c;那么今天给大家介绍一个新推出的Bioconductor软件包pathview。这个包可以进行KEGG富集分析。首先&#xff0c;我们不耐烦的介绍下Bioconductor包的安装…

【设计模式 02】策略模式( Strategy)

策略模式 参考&#xff1a; CSDN | 策略模式百家号 | 策略模式 如果某个系统需要不同的算法&#xff08;如超市收银的优惠算法&#xff09;&#xff0c;那么可以把这些算法独立出来&#xff0c;使之之间可以相互替换&#xff0c;这种模式叫做策略模式&#xff0c;它同样具有三个…

PL/SQL复合变量

复合变量可以将不同数据类型的多个值存储在一个单元中。由于复合类型可以由用户自己根据需要定义其结构&#xff0c;所以复合数据类型也称为自定义数据类型。在PL/SQL中&#xff0c;使用%TYPE声明的变量类型与数据表中字段的数据类型相同&#xff0c;当数据表中字段数据类型修改…

Android中使用am命令实现在命令行启动程序详解

在Android中&#xff0c;除了从界面上启动程序之外&#xff0c;还可以从命令行启动程序&#xff0c;使用的是命令行工具am. 复制代码代码如下:usage: am [subcommand] [options] start an Activity: am start [-D] -D: enable debugging send a broadcast Intent: am br…

用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…

node.js Websocket消息推送---GoEasy

Goeasy, 它是一款第三方推送服务平台&#xff0c;使用它的API可以轻松搞定实时推送&#xff01;个人感觉goeasy推送更稳定&#xff0c;推送速度快&#xff0c;代码简单易懂上手快浏览器兼容性&#xff1a;GoEasy推送支持websocket 和polling两种连接方式&#xff0c;从而可以支…

git 移动分支指针_理解git 中的HEAD指针branch指针

HEAD指针使用git checkout 来移动HEAD指针&#xff0c;移动的对象可以是分支指针也可以是快照。HEAD指针可以指向快照也可以指向branch。当指向branch时提交后会和branch指针一起向后移动&#xff0c;当不指向branch提交时时则会在一个detached状态。分支(branch)指针使用git b…

应用程序域

好文链接 使用.NET建立的可执行程序 .exe&#xff0c;并没有直接承载到进程当中&#xff0c;而是承载到应用程序域&#xff08;AppDomain&#xff09;当中。应用程序域是.NET引入的一个新概念&#xff0c;它比进程所占用的资源要少&#xff0c;可以被看作是一个轻量级的进程。 …

【设计模式 03】装饰模式——俄罗斯套娃?

装饰模式(俄罗斯套娃&#xff1f;) 装饰模式&#xff1a;动态的给某些对象添加额外的功能 参考&#xff1a; 简书 | 装饰模式 博客园 | 简说设计模式——装饰模式 博客园 | 装饰器模式 Decorator 结构型 设计模式 (十) 什么是装饰模式 装饰模式也叫装饰器模式&#xff0c;p…

系统移植的四大步骤

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

bem什么意思_bem是什么意思_bem的翻译_音标_读音_用法_例句_爱词霸在线词典

全部Methods Three different concentrations of the n - butanol extract of MOH ( BEM ) : 0.038 g L ~ ( - 1 ), 0.11 g L ~ ( - 1 ) and 0.34 g L ~ ( - 1 ) were prepared.巴戟天醇提取物 ( BEM ) 配成含生药0.038gL~ ( -1)、0.11gL~ ( -1 ) 、 0.34gL~ ( -1 ) 三种浓度…