一、并发同步问题

  线程安全是Java并发编程中的重点,而造成线程安全问题的主要原因有两点,一是存在共享数据(也称临界资源),二是存在多条线程共同操作共享数据。因此,当存在多个线程操作共享数据时,需要保证同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再进行,这种方式就叫互斥锁。也就是说当一个共享数据被正在访问的线程加上互斥锁后,在同一个时刻,其他线程只能处于等待的状态,直到当前线程处理完毕释放该锁。在 Java 中,关键字 synchronized可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块(主要是对方法或者代码块中存在共享数据的操作),同时synchronized还有另外一个重要的作用,它可以可保证一个线程的变化(主要是共享数据的变化)被其他线程所看到(保证可见性,完全可以替代Volatile功能)。

二、锁的简介

synchronized是Java的关键字,是一种同步锁。
  Java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁称为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
  Java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,直到线程B释放这个锁。
  Java的对象锁类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。
  在Java中,每个对象都有一把锁和两个队列,一个队列用于挂起未获得锁的线程,一个队列用于挂起条件不满足而等待的线程。而synchronized实际上也就是一个加锁和释放锁的集成。JVM负责跟踪对象被加锁的次数。如果一个对象被解锁,其计数变为0。在任务(线程)第一次给对象加锁的时候,计数变为1。每当这个相同的任务(线程)在此对象上获得锁时,计数会递增。只有首先获得锁的任务(线程)才能继续多次获取该对象上的锁。每当任务离开一个synchronized方法,计数递减,当计数为0的时候,锁被完全释放,此时别的任务就可以使用此资源。

三、synchronized的三种应用方式

synchronized可以修饰范围的包括:方法级别,代码块级别;而实际加锁的目标包括:对象锁(普通变量,静态变量),类锁。具体分为三种应用方式:

1.修饰一个实例方法

  被修饰的方法称为实例同步方法,其作用范围是整个方法锁定的是该方法所属的对象(即调用该方法的对象)。所有需要获得该对象锁的操作都会对该对象加锁(即访问该对象的其他同步实例方法或进入对该对象加锁的代码块)。实例同步方法的代码如下:

public synchronized void method(){// 具体代码
}

  当一个对象O1在不同的线程中执行这个同步方法时,他们之间会形成互斥,达到同步的效果。但是这个对象所属类的另一对象O2却能够调用这个被加了synchronized关键字的方法。 每个对象实例对应一把锁,线程只有获得对象实例的锁才能执行它的synchronized方法。 如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法。但是该类的其他对象实例的 synchronized方法是不相干扰的。这种机制确保了同一时刻对于每一个对象实例,其所有声明为 synchronized 的成员方法中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为synchronized)。上边的示例代码等同于如下代码:

public void method(){synchronized(this){//具体代码}
}

  其中this指的是调用这个方法的对象,如O1。可见同步方法实质是将synchronized作用于对象引用。只有获得O1对象锁的线程,才能够调用O1的同步方法,而对O2而言,O1对象锁和它互不关联,其他线程调用O2中的相同方法时,并不会产生同步阻塞。程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱。sychronized修饰方法时需要注意以下3点:

(1)synchronized关键字不能继承。

  虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,必须显式地在子类为这个方法加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。这两种方式的示例代码如下:
  手动加上synchronized修饰

class Parent{public synchronized void method() {}
}
class Child{public synchronized void method() {}
}

  在子类中调用父类同步方法

class Parent{public synchronized void method() {}
}
class Child{public synchronized void method() {}
}
(2)在定义接口方法时不能使用synchronized关键字。
(3)构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。
2.修饰一个静态方法

  被修饰的方法被称为静态同步方法,其作用的范围是整个静态方法,锁是静态方法所属的类(即Class对象)。所有需要获得该类的任意对象的锁,都会触发同步。静态同步方法的示例如下图:

上述代码中,虽然创建了SynThread类的两个对象,但是该类中的run方法调用的是静态同步方法,所以在运行过程中会同步执行。因此,synchronized作用在静态方法上时,可以防止多个线程同时访问这个类中的静态方法,它对类的所有实例对象都起作用。

3.修饰一个代码块

  被修饰的代码块称为同步语句块。synchronized的括号中必须传入一个对象(实例对象或类的Class对象)作为锁。其作用范围是大括号{}括起来的代码,锁是Synchronized括号里指定的内容。按照对象的类型可以分为类锁和对象锁。

(1)锁对象为实例对象
public void method(Object o) {   synchronized(o) {          ...  }
}

上述代码锁定的就是o这个对象,只要进入以该对象为锁的任何代码都会触发同步。当有一个明确的对象作为锁时,可以直接以该对象作为锁。当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的对象来充当锁。例如:

private byte[] lock = new byte[0];

注:查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。因此使用特殊对象来充当锁,大大节省了系统的开销。

(2)锁对象为类的Class对象
public class Demo{...public static void method(){synchronized(Demo.class){...}}
}

上述代码是以Demo类的Class对象为锁,进入以该类任意实例对象为锁的代码都会触发同步,其效果类似于静态同步方法。

四、synchronized的实现原理

monitor对象
  Java中的同步代码块是使用monitorenter和monitorexit指令实现的,其中monitorenter指令插入到同步代码块的开始位置,monitorexit指令插入到同步代码块的结束位置。JVM保证每一个monitorenter都有一个monitorexit与之相对应。任何对象都有一个monitor与之相关联,当线程执行到monitorenter指令时,将会尝试获取锁对象所对应的monitor所有权,即尝试获取对象的锁;当线程执行monitorexit指令时,锁的monitor就会被释放。同步方法的实现与同步块略有不同,它依靠的是方法修饰符上的ACC_SYNCHRONIZED实现。synchronized具体的实现原理详见本人另一篇文章:
深入理解Java中Synchronized的实现原理

五、Synchronized与重入锁ReentrantLock的区别

(1) 相对于ReentrantLock而言,synchronized锁是重量级锁,重量级体现在活跃性差一点。同时synchronized锁是内置锁,意味着JVM能基于synchronized锁做一些优化:比如增加锁的粒度(锁粗化)、锁消除。
(2) 在synchronized锁上阻塞的线程是不可中断的:线程A获得了synchronized锁,当线程B也去获取synchronized锁时会被阻塞。而且线程B无法被其他线程中断(不可中断的阻塞),而ReentrantLock锁能实现可中断的阻塞。
(3) synchronized锁释放是自动的,当线程执行退出synchronized锁保护的同步代码块时,会自动释放synchronized锁。而ReentrantLock需要显示地释放:即在try-finally块中释放锁。
(4) 线程在竞争synchronized锁时是非公平的:假设synchronized锁目前被线程A占有,线程B请求锁未果,被放入队列中,线程C请求锁未果,也被放入队列中,线程D也来请求锁,恰好此时线程A将锁释放了,那么线程D将跳过队列中所有的等待线程并获得这个锁。而ReentrantLock能够实现锁的公平性。
(5) synchronized锁是读写互斥并且读读也互斥,ReentrantReadWriteLock 分为读锁和写锁,而读锁可以同时被多个线程持有,适合于读多写少场景的并发。
(6) ReentrantLock锁的是代码块,synchronized还能锁方法和类。ReentrantLock可以知道线程有没有拿到锁,而synchronized不能。

六、总结

(1) synchronized如果修饰的是代码块,则根据传入内容决定锁的类型;synchronized如果修饰的是实例方法,它获取的锁是调用该方法的对象实例;synchronized如果修饰的是静态方法,它获取的锁是调用该方法所属的类,访问该类所有的同步模块都会加锁(每个对象的同步方法、类的同步方法)。
(2) synchronized同步的关键要看锁的类型
  如果是对象锁(即同步块传入对象作为锁或者实例同步方法),那么其他任意需要获得该对象锁的同步机制都会触发加锁。即线程进入一个同步实例方法,就会获得其所属对象的锁。此时,如果其他线程进入该对象其他的同步实例方法或同步代码块时就会阻塞。
  如果是类锁(即同步块传入类作为锁或者静态同步方法),那么其他任意需要获得该类锁的同步机制都会触发加锁。即线程进入一个静态同步方法,就会获得该方法所属类的锁。此时,如果其他线程再进入该类的其他同步方法(静态或非静态),或者进入获取该类锁的同步块,都会阻塞。
(3) 对象的内置锁和对象的状态之间没有内在的关联,虽然大多数类都将内置锁用作一种有效的加锁机制,但对象的域并不一定通过内置锁来保护。当获取到与对象关联的内置锁时,并不能阻止其他线程访问该对象,当某个线程获得对象的锁之后,只能阻止其他线程获得同一个锁。所以synchronized只是一个内置锁的加锁机制,当某个方法加上synchronized关键字后,就表明要获得该内置锁才能执行,并不能阻止其他线程访问不需要获得该内置锁的方法。
(4) 为什么要使用同步代码块?
  首先对程序来讲同步的部分很影响运行效率,同步所覆盖的代码越多,对效率的影响就越严重。因此我们通常尽量缩小其影响范围。所以就出现了同步代码块。只把一个方法中该同步的地方同步,并且同步代码块可以指定锁对象。
(5) 使用synchronized进入一个临界区,会获得对应的锁,退出时不管是否立即使用该对象的其他同步方法,都要立即释放锁,重新竞争获得。如何连续使用一个对象的所有同步方法,不用中途释放锁,可以创建一个线程,使用一个同步代码块,同步锁指定为该对象,然后在该代码块中可以连续调用该对象的同步方法。
(6) synchronized方法的缺陷
   a.实例同步方法锁定的是调用这个同步方法的对象。也就是说,一个对象P1在不同的线程中执行其同步方法时会产生互斥达到同步效果。但是P1对象所属类所创建的另一对象P2却可以调用这个同步方法。同步方法实质是将synchronized作用于对象引用。只有拿到P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱。
  b.若将一个代码量大的方法声明为synchronized将会大大影响效率。典型地,若将线程类的方法 run() 声明为 synchronized ,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何 synchronized 方法的调用都不会成功。

Java同步锁synchronized的最全总结相关推荐

  1. Java同步锁Synchronized底层源码和原理剖析

    目录 1 synchronized场景回顾 2 反汇编寻找锁实现原理 3 synchronized虚拟机源码 3.1 HotSpot源码Monitor生成 3.2 HotSpot源码之Monitor竞 ...

  2. [转]Java 对象锁-synchronized()与线程的状态与生命周期

    线程的状态与生命周期 Java 对象锁-synchronized() ? 1 2 3 4 synchronized(someObject){ //对象锁 } 对象锁的使用说明: 1.对象锁的返还. 当 ...

  3. 同步锁(synchronized)_37

    同步锁 概念: 把有可能出现问题的代码包起来,一次只让一个线程执行.通过sychronized关键字实现同步. 当多个对象操作共享数据时,可以使用同步锁解决线程安全问题. synchronized(对 ...

  4. java同步锁售票_Java基础学习笔记: 多线程,线程池,同步锁(Lock,synchronized )(Thread类,ExecutorService ,Future类)(卖火车票案例)...

    学习多线程之前,我们先要了解几个关于多线程有关的概念. 进程:进程指正在运行的程序.确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能. 线程:线程是 ...

  5. java同步锁synchronized_Java对象锁和类锁全面解析(多线程synchronized关键字)

    本文主要是将synchronized关键字用法作为例子来去解释Java中的对象锁和类锁.特别的是希望能帮大家理清一些概念. 一.synchronized关键字 synchronized关键字有如下两种 ...

  6. Java同步锁——lock与synchronized 的区别【转】

    在网上看来很多关于同步锁的博文,记录下来方便以后阅读 一.Lock和synchronized有以下几点不同: 1)Lock是一个接口,而synchronized是Java中的关键字,synchroni ...

  7. java同步锁如何使用_java 同步锁(synchronized)的正确使用姿势

    关于线程安全,线程锁我们经常会用到,但你的使用姿势正确不,反正我用错了好长一段时间而不自知.所以有了这篇博客总结下线程锁的正确打开姿势 废话不说看例子 一,对整个方法进行加锁 1,对整个方法进行加锁, ...

  8. java同步锁实例_Java lock同步锁使用实例解析

    这篇文章主要介绍了Java lock同步锁使用实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1)Lock是一个接口,而synchroniz ...

  9. java 同步锁_java线程中的同步锁和互斥锁有什么区别?

    在java中,同步锁和互斥锁英文关键字都是Synchronized,没有本质上的区别,两者都包括对资源的独占,使用起来没有区别.概念上的区别是 1:互斥是通过竞争对资源的独占使用,彼此没有什么关系,执 ...

  10. 你真的知道Java同步锁何时释放?

    在测试java多线程中有关 "生产者和消费者" 这个经典问题的时候,写代码测试的时候,思考到一些问题(所以还是要动手,实践才能储真知啊), synchronize 同步锁何时释放, ...

最新文章

  1. java逆向基础,Java逆向基础之函数
  2. html mint ui,vue mint-ui初次使用总结
  3. Nginx_虚拟主机配置讲解
  4. 经典的卷积神经网络简介
  5. CodeForces 1517G Starry Night Camping(网络流最小割)
  6. mysql查询不确定的信息_mysql 07.18
  7. js 之for..in、表单及事件触发
  8. 第四十九期:大牛总结的MySQL锁优化,写得太好了!
  9. 架构部署002--城域网_骨干网
  10. ubuntu没有指纹登录_Thinkpad在Ubuntu 20.04下使用指纹登陆
  11. 在线开关MySQL5.7 GTID_MySQL 5.7 在线启用和关闭GTID
  12. c++拷贝构造函数(深拷贝和浅拷贝)
  13. [UIImage _isCached]: message sent to deallocated instance
  14. GitHub 桌面版 v3.0 新特性「GitHub 热点速览 v.22.18」
  15. 将文件中的单词及翻译导入数据库
  16. 怎样一次性压缩多张图片?这个无损批量压缩图片方法送给你
  17. SQL中MINUS的用法
  18. FCPX插件:镜头光晕眩光特效插件PHYX Flarelight
  19. 高德地图基本开发流程
  20. SpringCache-redis缓存学习记录

热门文章

  1. 开源又好用的录屏软件
  2. 巨无霸Win8PE X64服务器维护专用,飞扬时空 Win8/Win10 PE 64位增强版
  3. C语言 标准库stdio.h
  4. Steam中如何下载锁区的游戏?一直出现 正在更新票证怎么办?
  5. 软件选择,iDreamPiano、freepiano、EveryonePiano
  6. studioOne安装教程与简单使用(图文)
  7. 数字图像处理第三版4.8.4例子GLPF高斯低通滤波器matlab程序
  8. 像素类沙盒游戏还存有哪些致命伤?
  9. stm32 OV7670/摄像头模块颜色区域定位(腐蚀中心算法)
  10. php 字符串长度的解释