Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 309|回复: 0

[Java线程学习]Java 多线程同步问题的探究(三)Lock来了

[复制链接]
  • TA的每日心情
    开心
    2021-3-12 23:18
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2014-11-2 23:59:18 | 显示全部楼层 |阅读模式
    在上一节中,
        我们已经了解了java多线程编程中常用的关键字synchronized,以及与之相关的对象锁机制。这一节中,让我们一起来认识JDK 5中新引入的并发框架中的锁机制。

    我想很多购买了《Java程序员面试宝典》之类图书的朋友一定对下面这个面试题感到非常熟悉:

    问:请对比synchronized与java.util.concurrent.locks.Lock 的异同。

    答案:主要相同点:Lock能完成synchronized所实现的所有功能
          主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。

    恩,让我们先鄙视一下应试教育。
       
      
       
       
         
       

         
       
      


      言归正传,我们先来看一个多线程程序。
    它使用多个线程对一个Student对象进行访问,
    改变其中的变量值。我们首先用传统的synchronized 机制来实现它:




      
       
      public
       
      class
       ThreadDemo
      implements
       Runnable {

         
      class
       Student {

             
      private
       
      int
       age
      =
       
      0
      ;

             
      public
       
      int
       getAge() {
                
      return
       age;
             }

             
      public
       
      void
       setAge(
      int
       age) {
                
      this
      .age
      =
       age;
             }
         }
         Student student
      =
       
      new
       Student();
         
      int
       count
      =
       
      0
      ;

         
      public
       
      static
       
      void
       main(String[] args) {
             ThreadDemo td
      =
       
      new
       ThreadDemo();
             Thread t1
      =
       
      new
       Thread(td,
      "
      a
      "
      );
             Thread t2
      =
       
      new
       Thread(td,
      "
      b
      "
      );
             Thread t3
      =
       
      new
       Thread(td,
      "
      c
      "
      );
             t1.start();
             t2.start();
             t3.start();
         }

         
      public
       
      void
       run() {
             accessStudent();
         }

         
      public
       
      void
       accessStudent() {
             String currentThreadName
      =
       Thread.currentThread().getName();
             System.out.println(currentThreadName
      +
       
      "
       is running!
      "
      );
             
      synchronized
       (
      this
      ) {
      //
      (1)使用同一个ThreadDemo对象作为同步锁
      

                  System.out.println(currentThreadName
      +
       
      "
       got lock1@Step1!
      "
      );
                
      try
       {
                     count
      ++
      ;
                     Thread.sleep(
      5000
      );
                 }
      catch
       (Exception e) {
                     e.printStackTrace();
                 }
      finally
       {
                     System.out.println(currentThreadName
      +
       
      "
       first Reading count:
      "
       
      +
       count);
                 }

             }
            
             System.out.println(currentThreadName
      +
       
      "
       release lock1@Step1!
      "
      );

             
      synchronized
       (
      this
      ) {
      //
      (2)使用同一个ThreadDemo对象作为同步锁
      

                  System.out.println(currentThreadName
      +
       
      "
       got lock2@Step2!
      "
      );
                
      try
       {
                     Random random
      =
       
      new
       Random();
                     
      int
       age
      =
       random.nextInt(
      100
      );
                     System.out.println(
      "
      thread
      "
       
      +
       currentThreadName
      +
       
      "
       set age to:
      "
       
      +
       age);

                     
      this
      .student.setAge(age);

                     System.out.println(
      "
      thread
      "
       
      +
       currentThreadName
      +
       
      "
       first  read age is:
      "
       
      +
       
      this
      .student.getAge());

                     Thread.sleep(
      5000
      );
                 }
      catch
       (Exception ex) {
                     ex.printStackTrace();
                 }
      finally
      {
                     System.out.println(
      "
      thread
      "
       
      +
       currentThreadName
      +
       
      "
       second read age is:
      "
       
      +
       
      this
      .student.getAge());
                 }

             }
             System.out.println(currentThreadName
      +
       
      "
       release lock2@Step2!
      "
      );
         }
    }
      
    转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/

    运行结果:




      
       
      a is running!
    a got lock1@Step1!
    b is running!
    c is running!
    a first Reading count:
      1
      
    a release lock1@Step1!
    a got lock2@Step2!
    thread a set age to:
      76
      
    thread a first  read age is:
      76
      
    thread a second read age is:
      76
      
    a release lock2@Step2!
    c got lock1@Step1!
    c first Reading count:
      2
      
    c release lock1@Step1!
    c got lock2@Step2!
    thread c set age to:
      35
      
    thread c first  read age is:
      35
      
    thread c second read age is:
      35
      
    c release lock2@Step2!
    b got lock1@Step1!
    b first Reading count:
      3
      
    b release lock1@Step1!
    b got lock2@Step2!
    thread b set age to:
      91
      
    thread b first  read age is:
      91
      
    thread b second read age is:
      91
      
    b release lock2@Step2!
    成功生成(总时间:
      30
       秒)

      

    显然,在这个程序中,
    由于两段synchronized块使用了同样的对象做为对象锁
    ,所以JVM优先使刚刚释放该锁的线程重新获得该锁。这样,
    每个线程执行的时间是10秒钟,
    并且要彻底把两个同步块的动作执行完毕,才能释放对象锁。这样,
    加起来一共是 30秒。


    转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/

    我想一定有人会说:
    如果两段synchronized块采用两个不同的对象锁,
    就可以提高程序的并发性,并且,
    这两个对象锁应该选择那些被所有线程所共享的对象。



    那么好。我们把第二个同步块中的对象锁改为student(
    此处略去代码,读者自己修改),程序运行结果为:




      
       
      a is running!
    a got lock1@Step1!
    b is running!
    c is running!
    a first Reading count:
      1
      
    a release lock1@Step1!
    a got lock2@Step2!
    thread a set age to:
      73
      
    thread a first  read age is:
      73
      
    c got lock1@Step1!
    thread a second read age is:
      73
      
    a release lock2@Step2!
    c first Reading count:
      2
      
    c release lock1@Step1!
    c got lock2@Step2!
    thread c set age to:
      15
      
    thread c first  read age is:
      15
      
    b got lock1@Step1!
    thread c second read age is:
      15
      
    c release lock2@Step2!
    b first Reading count:
      3
      
    b release lock1@Step1!
    b got lock2@Step2!
    thread b set age to:
      19
      
    thread b first  read age is:
      19
      
    thread b second read age is:
      19
      
    b release lock2@Step2!
    成功生成(总时间:
      21
       秒)
      

    从修改后的运行结果来看,显然,由于同步块的对象锁不同了,
    三个线程的执行顺序也发生了变化。
    在一个线程释放第一个同步块的同步锁之后,
    第二个线程就可以进入第一个同步块,而此时,
    第一个线程可以继续执行第二个同步块。这样,整个执行过程中,
    有10秒钟的时间是两个线程同时工作的。
    另外十秒钟分别是第一个线程执行第一个同步块的动作和最后一个线
    程执行第二个同步块的动作。相比较第一个例程,
    整个程序的运行时间节省了1/3。
    细心的读者不难总结出优化前后的执行时间比例公式:(n+1)/
    2n,其中n为线程数。如果线程数趋近于正无穷,
    则程序执行效率的提高会接近50%。
    而如果一个线程的执行阶段被分割成m个 synchronized
    块,并且每个同步块使用不同的对象锁,而同步块的执行时间恒定,
    则执行时间比例公式可以写作:((m- 1)n+1)/
    mn那么当m趋于无穷大时,线程数n趋近于无穷大,
    则程序执行效率的提升几乎可以达到100%。(显然,
    我们不能按照理想情况下的数学推导来给BOSS发报告,
    不过通过这样的数学推导,
    至少我们看到了提高多线程程序并发性的一种方案,
    而这种方案至少具备数学上的可行性理论支持。)


    转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/

    可见,使用不同的对象锁,在不同的同步块中完成任务,
    可以使性能大大提升。



    很多人看到这不禁要问:这和新的Lock框架有什么关系?



    别着急。我们这就来看一看。



    synchronized块的确不错,
    但是他有一些功能性的限制:

    1. 它无法中断一个正在等候获得锁的线程,
    也无法通过投票得到锁,如果不想等下去,也就没法得到锁。

    2.
    synchronized 块对于锁的获得和释放是在相同的堆栈帧
    中进行的。多数情况下,这没问题(而且与异常处理交互得很好),
    但是,确实存在一些更适合使用非块结构锁定的情况。


    转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/

    java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、
    性能特性或者锁定语义。



    JDK 官方文档中提到:

    ReentrantLock是“一个可重入的互斥锁 Lock,它具有与使用 synchronized  方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,
    但功能更强大。

    ReentrantLock 将由最近成功获得锁,并且还没有释放该锁的线程所拥有。
    当锁没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁并返回。如果当前线程已经拥有该锁,
    此方法将立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法来检查此情况是否发生。 ”



    简单来说,
    ReentrantLock有一个与锁相关的获取计数器,
    如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,
    然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized 的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续) synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized 块时,才释放锁。


    转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/

    ReentrantLock  类(重入锁)实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、
    定时锁等候和可中断锁等候的一些特性。此外,
    它还提供了在激烈争用情况下更佳的性能。(换句话说,
    当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)



    我们把上面的例程改造一下:




      
       
      public
       
      class
       ThreadDemo
      implements
       Runnable {

         
      class
       Student {

             
      private
       
      int
       age
      =
       
      0
      ;

             
      public
       
      int
       getAge() {
                
      return
       age;
             }

             
      public
       
      void
       setAge(
      int
       age) {
                
      this
      .age
      =
       age;
             }
         }
         Student student
      =
       
      new
       Student();
         
      int
       count
      =
       
      0
      ;
         ReentrantLock lock1
      = new ReentrantLock(false);
         ReentrantLock lock2 = new ReentrantLock(false

      );

         
      public
       
      static
       
      void
       main(String[] args) {
             ThreadDemo td
      =
       
      new
       ThreadDemo();
             
      for
       (
      int
       i
      =
       
      1
      ; i
      <=
       
      3
      ; i
      ++
      ) {
                 Thread t
      =
       
      new
       Thread(td, i
      +
       
      ""
      );
                 t.start();
             }
         }

         
      public
       
      void
       run() {
             accessStudent();
         }

         
      public
       
      void
       accessStudent() {
             String currentThreadName
      =
       Thread.currentThread().getName();
             System.out.println(currentThreadName
      +
       
      "
       is running!
      "
      );
             lock1.lock();
      //
      使用重入锁
      

              System.out.println(currentThreadName
      +
       
      "
       got lock1@Step1!
      "
      );
             
      try
       {
                 count
      ++
      ;
                 Thread.sleep(
      5000
      );
             }
      catch
       (Exception e) {
                 e.printStackTrace();
             }
      finally
       {
                 System.out.println(currentThreadName
      +
       
      "
       first Reading count:
      "
       
      +
       count);
                 lock1.unlock();
                 System.out.println(currentThreadName
      +
       
      "
       release lock1@Step1!
      "
      );
             }

             lock2.lock();
      //
      使用另外一个不同的重入锁
      

              System.out.println(currentThreadName
      +
       
      "
       got lock2@Step2!
      "
      );
             
      try
       {
                 Random random
      =
       
      new
       Random();
                
      int
       age
      =
       random.nextInt(
      100
      );
                 System.out.println(
      "
      thread
      "
       
      +
       currentThreadName
      +
       
      "
       set age to:
      "
       
      +
       age);

                
      this
      .student.setAge(age);

                 System.out.println(
      "
      thread
      "
       
      +
       currentThreadName
      +
       
      "
       first  read age is:
      "
       
      +
       
      this
      .student.getAge());

                 Thread.sleep(
      5000
      );
             }
      catch
       (Exception ex) {
                 ex.printStackTrace();
             }
      finally
       {
                 System.out.println(
      "
      thread
      "
       
      +
       currentThreadName
      +
       
      "
       second read age is:
      "
       
      +
       
      this
      .student.getAge());
                 lock2.unlock();
                 System.out.println(currentThreadName
      +
       
      "
       release lock2@Step2!
      "
      );
             }

         }
    }
      



    从上面这个程序我们看到:



    对象锁的获得和释放是由手工编码完成的,
    所以获得锁和释放锁的时机比使用同步块具有更好的可定制性。
    并且通过程序的运行结果(运行结果忽略,请读者根据例程自行观察),我们可以发现,
    和使用同步块的版本相比,结果是相同的。


    转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/

    这说明两点问题:

    1. 新的ReentrantLock的确实现了和同步块相同的语义功
    能。而对象锁的获得和释放都可以由编码人员自行掌握。

    2. 使用新的ReentrantLock,
    免去了为同步块放置合适的对象锁所要进行的考量。

    3. 使用新的ReentrantLock,
    最佳的实践就是结合try/finally块来进行。
    在try块之前使用lock方法,
    而在finally中使用unlock方法。


    转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/

    细心的读者又发现了:



    在我们的例程中,创建ReentrantLock实例的时候,
    我们的构造函数里面传递的参数是false。
    那么如果传递 true又回是什么结果呢?这里面又有什么奥秘呢?



    请看本节的续 ―――― Fair or Unfair? It is a question...




      
      
       
       

         
       

         
       
      
    复制代码

    源码下载:http://file.javaxxz.com/2014/11/2/235918359.zip
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2025-2-25 15:58 , Processed in 0.377522 second(s), 50 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表