synchronized 同步锁(java)实例解析 痛定思痛。 2022-06-13 14:39 214阅读 0赞 ## 0 引言 ## 在多线程应用场景中,同步锁是一种非常重要的机制,例如:ID号的分配,多个客户端分别与服务端建立连接,客户端并发请求的情况下,为提升吞吐量,服务端一般采用多线程处理请求,若无同步锁机制,不同线程分配到相同ID号的情况将不可避免,而这种情况与预期相违背。 ## 1.java多线程简述 ## Java中线程的创建一般有三种形式,最常见的是继承Thread类覆写run()方法的方式,此外还有两种,这个问题并非本文关注点,这里就不做展开了,我重点介绍一下继承Thread类覆写run()方法的方式: ### 【继承Thread类,重写该类的run()方法】 ### 如以下代码所示,继承Thread类,通过重写run()方法定义了一个新的线程类MyThread,其中run()方法的方法体代表了线程需要完成的任务,称之为【线程执行体】。当创建此线程类的对象时,一个新的线程得以创建,并进入到线程【新建状态】。通过调用线程对象引用的start()方法,使得该线程进入到【就绪状态】,此时此线程并不一定会马上得以执行,这取决于CPU调度时机,这个时机并没有明显的规律,表面上看似随机,内在机理与JVM有关,java官方并未就此说明,当然,某种程度上这并不重要。 public class ThreadIntroduction { public static void main(String[] args) { for(int index=0;index<3;index++) { MyThread temp=new MyThread(); temp.start(); } } } class MyThread extends Thread { @Override public void run() { for(int i=0;i<5;i++) { System.out.println(Thread.currentThread().getName()+": ["+i+"]"); } } } 我们看一下上面代码某一次的运行结果: ![Center][] 如上图所示,多线程场景下,线程的执行顺序(cpu调度的顺序)与程序中的客观顺序并没有直接关系,且每次运行结果都不相同,具有一定的随机性。 ## 2.java 同步锁机制介绍 ## java同步锁的关键字为:synchronized,其应用主要有两种方式:\[synchronized 方法\]和\[synchronized 块\],顾名思义,其区别在于作用范围不同,以下分别对两种方式进行介绍,首先我们看一个例子类: public class TestForSynchronized { static int ID=0; //测试方法01-synchronized块(对象级) public String setID_01() { synchronized(this) { ID++; return "setID_01() ID No.:"+ID; } } //测试方法02-synchronized块(类级别) public String setID_02() { synchronized(TestForSynchronized.class) { ID++; return "setID_02() ID No.:"+ID; } } //测试方法03-synchronized 方法 public synchronized String setID_03() { ID++; return "setID_03() ID No.:"+ID; } //普通方法 public String commonMethod() { return "commonMethod ID No."+ID; } } 如上程序所示,分别体现了synchronized块(对象级别和类级别),synchronized方法,普通方法的定义方式, ### 2.1.synchronized 方法 ### 通过在方法的定义中加入synchronized关键字来定义synchronized方法,例如: public synchronized String setID(),这就是一个返回值为String类型的synchronized方法,对于一个应用了synchronized机制的类来说,以上面定义的类TestForSynchronized 为例,它的每一个实例(对象)都具有单一的锁,当通过这个(实例)对象调用它的任何synchronized方法,或者这个实例(对象)执行synchronized块的时候,这个实例(对象)就会被加锁,即: * 在多线程场景下,对于某一个类实例(对象)tempObject,如果多个线程并发通过tempObject访问其synchronized方法或者synchronized块时,任何一个时刻只有一个线程处于可执行状态,因为同一时刻只有一个线程能够获取该实例的唯一的锁,其它线程都会被阻塞,直到synchronized方法返回或者synchronized块执行完毕,锁才会被占用的线程释放,此前被阻塞的线程才有机会获得该对象的锁; * 在多线程场景下,对于某一个类实例(对象)tempObject,如果一个线程获得tempObject的锁,在一个synchronized方法返回或者一个synchronized块执行完毕后,便会将锁释放,而不会继续持有锁,即使该线程接下来仍需执行该实例tempObject的其它synchronized方法或者synchronized块; 我们以具体的例子来验证一下上述介绍(类实例为上面定义的TestForSynchronized): 验证<1>中论点: public class MainClass { public static void main(String[] args) { // 创建10个线程来调用【同一个】TestForSynchronized实例(对象) TestForSynchronized temp=new TestForSynchronized(); for(int index=0;index<10;index++) { MyThread_01 thread=new MyThread_01(temp); thread.start(); } } } class MyThread_01 extends Thread { TestForSynchronized testObject; public MyThread_01(TestForSynchronized testObject) { this.testObject=testObject; } @Override public void run() { try { Thread.sleep(0); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"--"+testObject.commonMethod()); System.out.println(Thread.currentThread().getName()+"--"+testObject.setID_01()); } } 某次运行的输出结果: ![Center 1][] 验证<2>中论点: 将上面验证<1>中的commonMethod换成synchronized方法setID\_03: public class MainClass { public static void main(String[] args) { // 创建10个线程来调用【同一个】TestForSynchronized实例(对象) TestForSynchronized temp=new TestForSynchronized(); for(int index=0;index<10;index++) { MyThread_01 thread=new MyThread_01(temp); thread.start(); } } } class MyThread_01 extends Thread { TestForSynchronized testObject; public MyThread_01(TestForSynchronized testObject) { this.testObject=testObject; } @Override public void run() { try { Thread.sleep(0); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"--"+testObject.setID_01()); System.out.println(Thread.currentThread().getName()+"--"+testObject.setID_03()); } } 某次运行的输出结果: ![Center 2][] ### 2.2.synchronized 块 ### 通过上面的介绍,相信读者已经对synchronized块和synchronized方法有了一定理解,这里我们再补充介绍一下synchronized块。前已述及,synchronized块和synchronized方法,最明显的区别在于【作用域】,synchronized方法的作用域更大一些,某些场景下,作用域太大是一种缺陷,例如:对于一个很复杂,代码量比较大的方法,如果将其定义为synchronized方法,那么,由于同步锁的机制制约,多线程场景下,效率将会显得低下;如果,需要同步锁机制保障的仅仅只是一小段代码的话,完全可以采用synchronized块来解决。 synchronized块的定义方式:synchronized(syncObject) synchronized(syncObject) { //允许控制的代码块 } synchronized块具有以下特点: * synchronized块必须获得了syncObject的锁才能执行这块代码,具体获得锁的机制如前面分析; * 当多个线程并发访问某个实例syncObject的synchronized(this)块时,任何一个时刻只有一个线程能够持有该实例的锁,执行synchronized(this)块,其它线程将被阻塞,直到执行完毕释放锁; * 多线程场景下,当某个线程访问实例syncObject的synchronized(this)块时,其它线程可以访问实例syncObject的非synchronized(this)块和非synchronized方法; ### 2.3.特殊的synchronized 块(重要!!!) ### 如我们在例子类TestForSynchronized中定义的方法setID\_02(): //测试方法02-synchronized块(类级别) public String setID_02() { synchronized(TestForSynchronized.class) { ID++; return "setID_02() ID No.:"+ID; } } 在synchronized块中,并不是this(对象级),而是class(类级别),相较于对象级别,类级别具有更严格的同步约束,主要有以下几点: * 前已述及,采用了synchronized机制的类,其每一个实例都有一个锁(对象级别的锁),事实上,类也有唯一的锁,换言之,采用了synchronized机制的类有一个类锁; * 多线程场景下,当某一线程获得类锁(注意:不再是对象锁),其它线程将被阻塞,将无法调用或访问该类的所有方法和域,包括静态方法和静态变量; * 对于含有静态方法和静态变量的代码块的同步,类锁的严格约束在多线程场景下非常实用,应用也较多。 ## 3.类锁的应用实例 ## 基于在TestForSynchronized类中定义了synchronized块的方法setID\_02,运行如下代码: public class MainClass { public static void main(String[] args) { //创建10个线程,每个线程各创建一个TestForSynchronized实例(对象) for(int index=0;index<10;index++) { MyThread_02 thread=new MyThread_02(); thread.start(); } } } class MyThread_02 extends Thread { @Override public void run() { //每个线程均创建一个TestForSynchronized实例 TestForSynchronized temp=new TestForSynchronized(); System.out.println(Thread.currentThread().getName()+"--"+temp.setID_02()); } } 某次运行的输出结果: ![Center 3][] ## 4. 互斥锁mutex ## 上面介绍的“类锁”虽然可以最大限度的避免并发场景下的冲突,但是,其过于严格:多线程场景下,当某一线程获得类锁(注意:不再是对象锁),其它线程将被阻塞,将无法调用或访问该类的所有方法和域,包括静态方法和静态变量。 事实上,对于一个类,不可能所有属性和方法都涉及并发问题,因此,类锁过于严格的限制会极大的影响性能。鉴于此,本节介绍另外一种锁:互斥锁mutex,其定义方式如下: public class TestForSynchronized { //定义一个静态对象 private static Object mutex = new Object(); // //省略其它属性的定义 public void TestMethod() { synchronized (mutex) { //涉及并发问题的代码块 } } } 由于mutex为静态类型,对于TestForSynchronized类的所有对象,当需要访问TestMethod的同步块时都必须获得mutex对象的锁,然而,mutex属于类,有且仅有一个锁,从而可以保证任意一个时刻只有一个线程可以访问这个同步块。 ## 5.后记 ## 限于水平,难免疏漏,请看官多多指正,等到抽出时间再进行补充。 【特别说明】有任何疑问,请扫描二维码提问: ![20180516104402625][] [Center]: /images/20220613/e28cca7252cf47059f38d5ab1c2246d3.png [Center 1]: /images/20220613/a9056029ddb040a99dcc0eced950d027.png [Center 2]: /images/20220613/9ab54bcbcea04b5c9904e478a32ca20d.png [Center 3]: /images/20220613/d17c0bd3976b49ddb703232766c41e0a.png [20180516104402625]: /images/20220613/aef167df8f3547618f54d5da56fb252b.png
相关 Java多线程同步:CountDownLatch实例解析 `CountDownLatch`是Java并发工具包(java.util.concurrent)中的一个类,主要用于多线程间的协调和同步。 当一个任务完成时,它会调用`cou 深碍√TFBOYSˉ_/ 2024年09月15日 17:42/ 0 赞/ 8 阅读
相关 Java多线程:同步问题的实例解析 在Java多线程编程中,同步问题是常遇到的问题。这里我们将通过几个实例来解析和理解同步问题。 **1. 线程共享资源** 场景:多个线程同时访问和修改一个共享资源(如数组、 た 入场券/ 2024年09月11日 04:51/ 0 赞/ 19 阅读
相关 Java并发库:锁和同步问题的实例解析 在Java中,线程之间的协作主要通过锁(Lock)和同步机制实现。以下是一些具体的实例解析: 1. **独占锁(Synchronized)**: ```java 女爷i/ 2024年09月05日 19:48/ 0 赞/ 9 阅读
相关 Java多线程同步问题实例解析 在Java中,多线程同步是处理并发访问共享资源的重要手段。下面通过一个实例来解析: 实例:有一个生产者(Producer)和多个消费者(Consumer),生产者负责产生商品 谁借莪1个温暖的怀抱¢/ 2024年09月05日 16:30/ 0 赞/ 18 阅读
相关 Java多线程同步问题实例解析 在Java编程中,多线程同步是一个核心问题。以下通过一个实际案例来解析这个问题。 **案例:银行取款机** 假设我们有一个银行的自动取款机(ATM),它有多个取款通道供用户 蔚落/ 2024年09月05日 15:15/ 0 赞/ 14 阅读
相关 Java中Synchronized 同步锁 Synchronized 同步锁 synchronized 它可以把任意一个非 NULL 的对象当作锁。他属于独占式的悲观锁,同时属于可重入锁。 Synchronize - 日理万妓/ 2024年03月22日 18:11/ 0 赞/ 9 阅读
相关 死磕 java同步系列之synchronized解析 问题 (1)synchronized的特性? (2)synchronized的实现原理? (3)synchronized是否可重入? (4)synchronized 你的名字/ 2022年10月01日 11:44/ 0 赞/ 206 阅读
相关 synchronized 同步锁(java)实例解析 0 引言 在多线程应用场景中,同步锁是一种非常重要的机制,例如:ID号的分配,多个客户端分别与服务端建立连接,客户端并发请求的情况下,为提升吞吐量,服务端一般采用多 痛定思痛。/ 2022年06月13日 14:39/ 0 赞/ 215 阅读
相关 死磕 java同步系列之synchronized解析 问题 (1)synchronized的特性? (2)synchronized的实现原理? (3)synchronized是否可重入? (4)synchronized 红太狼/ 2022年01月29日 04:57/ 0 赞/ 283 阅读
还没有评论,来说两句吧...