synchronized

英 [’sɪŋkrənaɪzd] 美 [’sɪŋkrənaɪzd]
同步的
网络
同步; 关键字; 同步锁

同步的原理

monitor 监测仪;  显示屏,屏幕; 
JVM规范规定JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用monitorenter和monitorexit指令实现,而方法同步是使用另外一种方式实现的,细节在JVM规范里并没有详细说明,但是方法的同步同样可以使用这两个指令来实现。monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处, JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个 monitor 与之关联,当且一个monitor 被持有后,它将处于锁定状态。线程执行到 monitorenter 指令时,将会尝试获取对象所对应的 monitor 的所有权,即尝试获得对象的锁。


上面是这个词汇的翻译,从翻译上大概的知道,这个东西是做什么用的吧!
在我们的实际应用当中可能经常会遇到这样一个场景:多个线程读或者、写相同的数据,访问相同的文件等等。对于这种情况如果我们不加以控制,是非常容易导致错误的。在java中,为了解决这个问题,引入临界区概念。所谓临界区是指一个访问共用资源的程序片段,而这些共用资源又无法同时被多个线程访问。

在java中为了实现临界区提供了同步机制。当一个线程试图访问一个临界区时,他将使用一种同步机制来查看是不是已经有其他线程进入临界区。如果没有则他就可以进入临界区,否则他就会被同步机制挂起,指定进入的线程离开这个临界区。

临界区规定:每次只准许一个进程进入临界区,进入后不允许其他进程进入。调度法则为:

1、如果有若干进程要求进入空闲的临界区,一次仅允许一个进程进入。

2、任何时候,处于临界区内的进程不可多于一个。如已有进程进入自己的临界区,则其它所有试图进入临界区的进程必须等待。

3、进入临界区的进程要在有限时间内退出,以便其它进程能及时进入自己的临界区。

4、如果进程不能进入自己的临界区,则应让出CPU,避免进程出现“忙等”现象。

使用synchronized关键字来实现同步机制。

简介

synchronized,我们谓之锁,主要用来给方法、代码块加锁。当某个方法或者代码块使用synchronized时,那么在同一时刻至多仅有有一个线程在执行该段代码。当有多个线程访问同一对象的加锁方法/代码块时,同一时间只有一个线程在执行,其余线程必须要等待当前线程执行完之后才能执行该代码段。但是,其余线程是可以访问该对象中的非加锁代码块的。

synchronized主要包括两种方法:synchronized 方法、synchronized 块。

通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。如:

public synchronized void getTest();  

synchronized方法控制对类成员变量的访问。它是如何来避免类成员变量的访问控制呢?我们知道方法使用了synchronized关键字表明该方法已加锁,在任一线程在访问改方法时都必须要判断该方法是否有其他线程在“独占”。每个类实例对应一个把锁,每个synchronized方法都必须调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,被阻塞的线程方能获得该锁。

其实synchronized方法是存在缺陷的,如果我们将一个很大的方法声明为synchronized将会大大影响效率的。如果多个线程在访问一个synchronized方法,那么同一时刻只有一个线程在执行该方法,而其他线程都必须等待,但是如果该方法没有使用synchronized,则所有线程可以在同一时刻执行它,减少了执行的总时间所以如果我们知道一个方法不会被多个线程执行到或者说不存在资源共享的问题,则不需要使用synchronized关键字。但是如果一定要使用synchronized关键字,那么我们可以synchronized代码块来替换synchronized方法


synchronized 块

synchronized代码块所起到的作用和synchronized方法一样,只不过它使临界区变的尽可能短了,换句话说:它只把需要的共享数据保护起来,其余的长代码块留出此操作。语法如下:

synchronized(object) {    //允许访问控制的代码
}
我们只要对于对象进行区分就好了,可以使用XXX.class 或者this关键字
对于这个锁起来就好啦!

对于synchronized(this)有如下理解:

1、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

2、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问object中的非synchronized(this)同步代码块。

3、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其他synchronized(this)同步代码块得访问将被阻塞。

4、第三个例子同样适用其他同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其他线程对该object对象所有同步代码部分的访问都将被暂时阻塞。

5、以上规则对其他对象锁同样适用

知道了概念很清楚,书写代码的时候才不会犯错。这点很重要,不说我们,很多老师对于并发问题都感觉很棘手,经验很重要啊。

进阶,怎么锁

在java多线程中存在一个“先来后到”的原则,也就是说谁先抢到钥匙,谁先用。我们知道为避免资源竞争产生问题,java使用同步机制来避免,而同步机制是使用锁概念来控制的。那么在Java程序当中,锁是如何体现的呢?这里我们需要弄清楚两个概念:

什么是锁?在日常生活中,它就是一个加在门、箱子、抽屉等物体上的封缄器,防止别人偷窥或者偷盗,起到一个保护的作用。在java中同样如此,锁对对象起到一个保护的作用,一个线程如果独占了某个资源,那么其他的线程别想用,想用?等我用完再说吧!

在java程序运行环境中,JVM需要对两类线程共享的数据进行协调:

1、保存在堆中的实例变量

2、保存在方法区中的类变量。

在java虚拟机中,每个对象和类在逻辑上都是和一个监视器相关联的。对于对象来说,相关联的监视器保护对象的实例变量。 对于类来说,监视器保护类的类变量。如果一个对象没有实例变量,或者说一个类没有变量,相关联的监视器就什么也不监视。

为了实现监视器的排他性监视能力,java虚拟机为每一个对象和类都关联一个锁。代表任何时候只允许一个线程拥有的特权。线程访问实例变量或者类变量不需锁。 如果某个线程获取了锁,那么在它释放该锁之前其他线程是不可能获取同样锁的。一个线程可以多次对同一个对象上锁。对于每一个对象,java虚拟机维护一个加锁计数器,线程每获得一次该对象,计数器就加1,每释放一次,计数器就减 1,当计数器值为0时,锁就被完全释放了。
java编程人员不需要自己动手加锁,对象锁是java虚拟机内部使用的。在java程序中,只需要使用synchronized块或者synchronized方法就可以标志一个监视区域。当每次进入一个监视区域时,java 虚拟机都会自动锁上对象或者类
好高深~慢慢的理解就好了,没有一蹴而就,慢慢的积累新的知识!

锁的是什么?

在这个问题之前我们必须要明确一点:无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象。在java中每一个对象都可以作为锁,它主要体现在下面三个方面:

对于同步方法,锁是当前实例对象。

对于同步方法块,锁是Synchonized括号里配置的对象。

对于静态同步方法,锁是当前对象的Class对象

例子:

package ThredDemo;/*** 线程类* * @author JetWang**/
public class SyDemo implements Runnable {public static void main(String[] args) {for (int i = 0; i < 5; i++) {new Thread(new SyDemo(), "thread_" + i).start();// 多个线程组}}@Overridepublic synchronized void run() {// TODO Auto-generated method stubfor (int i = 0; i < 3; i++) {System.out.println(Thread.currentThread().getName() + "   going");}}}

thread_2 going
thread_4 going
thread_4 going
thread_0 going
thread_0 going
thread_3 going
执行的结果和我们的想法,不一致啊?惊呆啦
照理来说,run方法加上synchronized关键字后,会产生同步效果,这些线程应该是一个接着一个执行run方法的。一个成员方法加上synchronized关键字后,实际上就是给这个成员方法加上锁,具体点就是以这个成员方法所在的对象本身作为对象锁但是在这个实例当中我们一共new了5个SyDemo对象,那个每个线程都会持有自己线程对象的对象锁,这必定不能产生同步的效果。所以:如果要对这些线程进行同步,那么这些线程所持有的对象锁应当是共享且唯一的

这个时候synchronized锁住的是那个对象?它锁住的就是调用这个同步方法对象。就是说SyDemo这个对象在不同线程中执行同步方法,就会形成互斥。达到同步的效果,修改如下:

package ThredDemo;/*** 线程类* * @author JetWang**/
public class SyDemo implements Runnable {public static void main(String[] args) {SyDemo demo=new SyDemo();for (int i = 0; i < 5; i++) {new Thread(demo,"thread_" + i).start();// 多个线程组}}@Overridepublic synchronized void run() {// TODO Auto-generated method stubfor (int i = 0; i < 3; i++) {System.out.println(Thread.currentThread().getName() + "   going");}}}

thread_0 going
thread_0 going
thread_0 going
thread_3 going
thread_3 going
thread_3 going
thread_4 going
thread_4 going
thread_4 going
thread_2 going
thread_2 going
thread_2 going
thread_1 going
thread_1 going
thread_1 going

多个线程,访问的是同一个对象的时候,对象锁就起作用啦,按照顺序的访问我们的代码!

对于同步方法,锁是当前实例对象。


synchronized代码块

package ThredDemo;
/*** * @author JetWang**/
public class SyDemo_2 extends Thread {public String lock;public SyDemo_2( String name,String lock) {super();this.lock = lock;this.name = name;}public String name;@Overridepublic void run() {// TODO Auto-generated method stubsynchronized (lock) {for (int i = 0; i < 3; i++) {System.out.println(name + " run......");}}}public static void main(String[] args) {// TODO Auto-generated method stubString lock = new String("test");for (int i = 0; i < 5; i++) {new SyDemo_2("Demo" + i, lock).start();}}}

Demo0 run……
Demo0 run……
Demo0 run……
Demo3 run……
Demo3 run……
Demo3 run……
Demo2 run……
Demo2 run……
Demo2 run……
Demo4 run……
Demo4 run……
Demo4 run……
Demo1 run……
Demo1 run……
Demo1 run……
好像成功啦,锁住了Lock为啥子呢?这个又要去说一下啦,lock说明是唯一的,不可以多个不同的。
如果我把lock,改为this结果怎么样,肯定错了吧,我这里生成了多个对象,肯定不一样所以没同步 this
Demo0 run……
Demo3 run……
Demo4 run……
Demo1 run……
Demo4 run……
Demo3 run……
Demo2 run……
Demo0 run……
Demo2 run……
Demo3 run……
Demo4 run……
Demo1 run……
Demo2 run……
Demo0 run……
Demo1 run……
接着呢,我还是不服气,我做了个变化,改为字节码SyDemo.class.这次成功啦,为啥?我们肯定只存在一个字节码啊,怎么肯定会出现两个呢!
这里的Lock也是一样的,为啥呢?
String 不可变的字符串,放的位置在哪?我们知道java中存在一个字符串池,那么这些线程的lock私有变量实际上指向的是堆内存中的同一个区域,即存放main函数中的lock变量的区域,所以对象锁是唯一且共享的。线程同步!!

只要我们锁住的对象是唯一的就好啦,再来看个例子,这个其实也差不多,因为静态方法是类所有的,不是对象所有的。所以唯一的可以实现同步

package ThredDemo;
/*** * @author JetWang**/
public class SyDemo3 extends Thread {public static synchronized void test(){for(int i = 0 ; i < 3 ; i++){  System.out.println(Thread.currentThread().getName() + " run......");  }  }@Overridepublic void run() {// TODO Auto-generated method stubtest();}public static void main(String[] args) {// TODO Auto-generated method stubfor(int i=0;i<3;i++){new SyDemo3().start();}}}

Thread-0 run……
Thread-0 run……
Thread-0 run……
Thread-1 run……
Thread-1 run……
Thread-1 run……
Thread-2 run……
Thread-2 run……
Thread-2 run……

在这个实例中,run方法使用的是一个同步方法,而且是static的同步方法,那么这里synchronized锁的又是什么呢?我们知道static超脱于对象之外,它属于类级别的。所以,对象锁就是该静态放发所在的类的Class实例。由于在JVM中,所有被加载的类都有唯一的类对象,在该实例当中就是唯一的 SyDemo3.class对象。不管我们创建了该类的多少实例,但是它的类实例仍然是一个!所以对象锁是唯一且共享的。线程同步!!

对于静态同步方法,锁是当前对象的Class对象。

http://ifeve.com/java-synchronized/ 这个下面讲解好多的锁,有点晕!看过JVM的大概的知道点,让我讲野说不清楚~想了解的自己去看吧!
锁自旋
我们知道在当某个线程在进入同步方法/代码块时若发现该同步方法/代码块被其他现在所占,则它就要等待,进入阻塞状态,这个过程性能是低下的。

在遇到锁的争用或许等待事,线程可以不那么着急进入阻塞状态,而是等一等,看看锁是不是马上就释放了,这就是锁自旋。锁自旋在一定程度上可以对线程进行优化处理。

偏向锁

偏向锁主要为了解决在没有竞争情况下锁的性能问题。在大多数情况下锁锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。当某个线程获得锁的情况,该线程是可以多次锁住该对象,但是每次执行这样的操作都会因为CAS(CPU的Compare-And-Swap指令)操作而造成一些开销消耗性能,为了减少这种开销,这个锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步。
当有其他线程在尝试着竞争偏向锁时,持有偏向锁的线程就会释放锁

锁膨胀
多个或多次调用粒度太小的锁,进行加锁解锁的消耗,反而还不如一次大粒度的锁调用来得高效。

轻量级锁
轻量级锁能提升程序同步性能的依据是“对于绝大部分的锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。轻量级锁在当前线程的栈帧中建立一个名为锁记录的空间,用于存储锁对象目前的指向和状态。如果没有竞争,轻量级锁使用CAS操作避免了使用互斥量的开销,但如果存在锁竞争,除了互斥量的开销外,还额外发生了CAS操作,因此在有竞争的情况下,轻量级锁会比传统的重量级锁更慢。

码农小汪-synchronized相关推荐

  1. 码农小汪-设计模式之-Builder模式

    建造者模式 将一个复杂的对象的构建与它的表示分离,使得同样构建的过程中可以创建不同的表示.这个话语看起来,好累啊!真心很难理解. 下面是它的UML图: 抽象建造者角色(Builder):为创建一个Pr ...

  2. 码农小汪-Volatile和Transient

    Volatile: Volatile修饰的成员变量在每次被线程访问时,都强迫从主内存中重读该成员变量的值.而且,当成员变量发生变化时,强迫线程将变化值回写到主内存.这样在任何时刻,两个不同的线程总是看 ...

  3. 码农小汪-Hibernate学习8-hibernate关联关系注解表示@OneToMany mappedBy @ManyToMany @JoinTable...

    近期我也是有点郁闷,究竟是程序中处理关联关系.还是直接使用外键处理关联关系呢?这个的说法不一致!程序中处理这样的关联关系的话.自己去维护这样的约束.这样的非常乐观的一种做法!或者是直接在数据库中处理这 ...

  4. 码农小汪-设计模式之-命令模式

    大话设计模式的例子讲的非常的好,理解起来也方便!有时候忘了.想到这些特殊的例子感觉就是特别爽. 烤羊肉串带来的思考! 路边摊羊肉串: 老板,我这里排的比较先啊,我最先给钱.老板这个没有熟啊.我的是 辣 ...

  5. 码农翻身全年文章精华

    在码农翻身公众号写了一年多, 最大的体会就是:原创真心不易! 每天思考的最大问题就是: 下一篇文章写啥? 在大家的支持和鼓励下,还是坚持了下来,  回头看看走过的路,这一年过得还算充实. 很快就要过年 ...

  6. 码农翻身 各章节链接

    大话编程 我是一个线程 我是一个Java class Javascript: 一个屌丝的逆袭 Java:一个帝国的诞生 JSP:一个装配工的没落 TCP/IP 之 大明王朝的邮差 TCP/IP 之 大 ...

  7. 码农翻身全年文章精华2016

    在码农翻身公众号写了一年多, 最大的体会就是:原创真心不易! 每天思考的最大问题就是: 下一篇文章写啥? 在大家的支持和鼓励下,还是坚持了下来,  回头看看走过的路,这一年过得还算充实. 很快就要过年 ...

  8. 德国码农开发抢厕纸神器,再也不用愁了

    ,点击 欧盟IT那些事 关注我们 公告:因企鹅审核规定,本公众号从<德国IT那些事>更名为<欧盟IT那些事>. 厕纸在手,天下我有! 疫情中由于欧美人民对厕纸的蜜汁喜爱,导致超 ...

  9. 身价过亿的冰山御萝对小码农说你液晶能显示吗

    文章目录 小码农为了御萝,咳咳,为了知识怎么敢说液晶不显示啊 ==联动文章 [身价过亿的冰山御萝的姐姐对小码农说你有大一点的液晶吗](https://blog.csdn.net/diandengren ...

  10. 身价过亿的妖媚子对小码农说串口能传送我的爱吗?

    文章目录 小码农不敢接受妖媚子(想多活几年),但是串口还是可以传送数据的 并行通信 串行通信 ==同步:== ==异步:== 串口通信:收发一个字节(只能用时间来同步) 串口通讯的相关术语 STC串口 ...

最新文章

  1. 敏捷开发必备的管理工具
  2. 【看完请推荐】记国庆前的一次码农受骗记
  3. 【物联网智能网关-02】获取摄像头数据+显示
  4. 2021-06-082021年春季学期-信号与系统-第十五次作业-第四小题参考答案
  5. perl中的几个模块使用.
  6. XslTransform.Transform方法将结果输出到字符串
  7. 台式计算机是32位还64位,怎么看电脑是32位还是64位
  8. 【机器学习基础】回归相关指标优化​
  9. 在SQLSERVER EXPRESS 2005中安装northwind和pubs数据库。
  10. Android http 的使用
  11. Flink 实时计算 - 维表 Join 解读
  12. java获取客服列表,java-从列表中获取处理案例的服务
  13. 查看数据库中存在触发器的表
  14. 为发泄对上司不满,百度95后程序员删库被判9个月
  15. 10. Zigbee应用程序框架开发指南 - 命令处理和生成
  16. H无穷控制学习笔记——H无穷/H2控制
  17. 中国卸扣市场趋势报告、技术动态创新及市场预测
  18. 基于MATLAB的分子动力学自编程序—300K下Ar的分子运动
  19. 18日精读掌握《费曼物理学讲义-卷一》计划(2019/6/12-2019/6/29)
  20. css3直线运动_css3动画--边框线条动画

热门文章

  1. g9008v android7,三星G9008V Android 4.4 (GALAXY S5 移动4G)图文刷机教程,最新教程,简单明了...
  2. IDEA、MySQL、SQLyog安装教程
  3. wps表格l制作甘特图_如何在表格中制作甘特图(横道图)?
  4. 用C语言数组编写贪吃蛇
  5. css美化滚动条样式,css3美化滚动条样式
  6. 分享免费下载论文的网站
  7. 时间序列谐波分析(HANTS)的MATLAB实现
  8. 菜刀php教程,中国菜刀(chopper)功能介绍及使用教程
  9. 老化测试相关知识点介绍
  10. 共享OrCAD9.2pSpice9.2+multisim下载地址