`
frank-liu
  • 浏览: 1682521 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

java concurrency: synchronized vs lock

 
阅读更多

简介

        在java的多线程编程中,需要考虑的最多的情况莫过于线程之间的同步和通信了。在线程的同步机制中,最常用的莫过于synchronized和lock。从更深层次的比较来说,他们有什么特点呢?在开发的时候到底哪种方式比较合适?我们就详细的了解一下吧。

synchronized简介

        一提起synchronized,似乎太简单了。在任何需要多线程访问的情况下,如果要对所访问的数据进行限制,保证每次只有一个线程可以操作该数据,我们可以在数据或者方法部分加一个synchronized。synchronized主要有两种使用的方式,一种是在一个方法内部用synchronized封装的代码块,可以称之为synchronized声明,还有一种是synchronized方法,主要是用于修饰一个方法。

两者的使用方式分别如下:

 

synchronized声明:

 

synchronized(lockObject)
{
    // put your stuff here
}

 

 

synchronized方法:

 

synchronized void doSomething()
{
      // Here is the business code.
}

 

光看这两种使用方式,似乎太简单了,没什么好说的。在更深层次里,jvm用了一些特别的手法来实现synchronized的特性。

 

更进一步分析

Monitor

        synchronized在jvm内部的实现是通过一种monitor的机制。synchronized在编译后会生成monitorenter和monitorexit这两个字节码。这两个字节码都需要一个引用类型的参数来指定要加锁和解锁的对象。如果synchronized指明了参数对象,也就是采用synchronized声明的方式,则该对象就是要加锁和后续解锁的对象。如果synchronized修饰的是方法,则根据方法对应的对象或者类来获取加锁和解锁对象。如果该方法是某个对象的,则对对象进行操作,如果是类方法,则获取该方法所在类的Class对象。

        系统生成的monitorenter和monitorexit正好封装了我们要同步操作的那部分代码块。

        我们来看一个示例,分别采用synchronized声明和synchronized方法实现。

下面是synchronized方法实现的代码:

class Prompter
{
    int delay;

    Prompter(int d)
    {
        if(d <= 0) d = 1;
        delay = d;
    }

    synchronized void display(String msg)
    {
        for(int i = 0; i < msg.length(); i++)
        {
            System.out.print(msg.charAt(i));

            if(Character.isWhitespace(msg.charAt(i)))
            {
                try
                {
                    Thread.sleep(delay * 1000);
                }
                catch(InterruptedException exc)
                {
                    return;
                }
            }
        }
        System.out.println();
    }
}

class UsePrompter implements Runnable
{
    Prompter prompter;
    String message;

    UsePrompter(Prompter p, String msg)
    {
        prompter = p;
        message = msg;
        new Thread(this).start();
    }

    public void run()
    {
        prompter.display(message);
    }
}

class SyncDemo
{
    public static void main(String[] args)
    {
        Prompter p = new Prompter(1);

        UsePrompter promptA = new UsePrompter(p, "One Two Three Four");
        UsePrompter promptB = new UsePrompter(p, "Left Right Up Down");
    }
}

 

我们定义了设定同步的地方在Prompter的display方法。两个线程分别调用Prompter对象的display方法。这时,如果我们反编译生成的Prompter class文件,会生成如下的结果:

 

Compiled from "SyncDemo.java"
class Prompter {
  int delay;

  Prompter(int);
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: iload_1       
       5: ifgt          10
       8: iconst_1      
       9: istore_1      
      10: aload_0       
      11: iload_1       
      12: putfield      #2                  // Field delay:I
      15: return        

  synchronized void display(java.lang.String);
    Code:
       0: iconst_0      
       1: istore_2      
       2: iload_2       
       3: aload_1       
       4: invokevirtual #3                  // Method java/lang/String.length:()I
       7: if_icmpge     55
      10: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      13: aload_1       
      14: iload_2       
      15: invokevirtual #5                  // Method java/lang/String.charAt:(I)C
      18: invokevirtual #6                  // Method java/io/PrintStream.print:(C)V
      21: aload_1       
      22: iload_2       
      23: invokevirtual #5                  // Method java/lang/String.charAt:(I)C
      26: invokestatic  #7                  // Method java/lang/Character.isWhitespace:(C)Z
      29: ifeq          49
      32: aload_0       
      33: getfield      #2                  // Field delay:I
      36: sipush        1000
      39: imul          
      40: i2l           
      41: invokestatic  #8                  // Method java/lang/Thread.sleep:(J)V
      44: goto          49
      47: astore_3      
      48: return        
      49: iinc          2, 1
      52: goto          2
      55: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      58: invokevirtual #10                 // Method java/io/PrintStream.println:()V
      61: return        
    Exception table:
       from    to  target type
          32    44    47   Class java/lang/InterruptedException
}

 现在,我们再看看另外一个synchronized声明的版本的代码:

class Prompter
{
    int delay;

    Prompter(int d)
    {
        if(d <= 0) d = 1;
        delay = d;
    }

    public void display(String msg)
    {
		synchronized(this)
		{
		    for(int i = 0; i < msg.length(); i++)
		    {
		        System.out.print(msg.charAt(i));

		        if(Character.isWhitespace(msg.charAt(i)))
		        {
		            try
		            {
		                Thread.sleep(delay * 1000);
		            }
		            catch(InterruptedException exc)
		            {
		                return;
		            }
		        }
		    }
		    System.out.println();
		}
    }
}

class UsePrompter implements Runnable
{
    Prompter prompter;
    String message;

    UsePrompter(Prompter p, String msg)
    {
        prompter = p;
        message = msg;
        new Thread(this).start();
    }

    public void run()
    {
        prompter.display(message);
    }
}

class SyncDemo
{
    public static void main(String[] args)
    {
        Prompter p = new Prompter(1);

        UsePrompter promptA = new UsePrompter(p, "One Two Three Four");
        UsePrompter promptB = new UsePrompter(p, "Left Right Up Down");
    }
}

代码几乎和前面的一样,唯一的差别就是将原来的synchronized display方法修改成了synchronized(this)的声明。这样,我们相当于对Prompter对象加锁。

再看看对Prompter class反编译的结果:

Compiled from "SyncDemo.java"
class Prompter {
  int delay;

  Prompter(int);
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: iload_1       
       5: ifgt          10
       8: iconst_1      
       9: istore_1      
      10: aload_0       
      11: iload_1       
      12: putfield      #2                  // Field delay:I
      15: return        

  public void display(java.lang.String);
    Code:
       0: aload_0       
       1: dup           
       2: astore_2      
       3: monitorenter  
       4: iconst_0      
       5: istore_3      
       6: iload_3       
       7: aload_1       
       8: invokevirtual #3                  // Method java/lang/String.length:()I
      11: if_icmpge     62
      14: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      17: aload_1       
      18: iload_3       
      19: invokevirtual #5                  // Method java/lang/String.charAt:(I)C
      22: invokevirtual #6                  // Method java/io/PrintStream.print:(C)V
      25: aload_1       
      26: iload_3       
      27: invokevirtual #5                  // Method java/lang/String.charAt:(I)C
      30: invokestatic  #7                  // Method java/lang/Character.isWhitespace:(C)Z
      33: ifeq          56
      36: aload_0       
      37: getfield      #2                  // Field delay:I
      40: sipush        1000
      43: imul          
      44: i2l           
      45: invokestatic  #8                  // Method java/lang/Thread.sleep:(J)V
      48: goto          56
      51: astore        4
      53: aload_2       
      54: monitorexit   
      55: return        
      56: iinc          3, 1
      59: goto          6
      62: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      65: invokevirtual #10                 // Method java/io/PrintStream.println:()V
      68: aload_2       
      69: monitorexit   
      70: goto          80
      73: astore        5
      75: aload_2       
      76: monitorexit   
      77: aload         5
      79: athrow        
      80: return        
    Exception table:
       from    to  target type
          36    48    51   Class java/lang/InterruptedException
           4    55    73   any
          56    70    73   any
          73    77    73   any
}
 

如果我们仔细去看两种方式反编译后的结果,会发现除了synchronized声明中增加了一个monitorenter和monitorexit外,几乎没什么差别。两者有这么一个细微的差别是在于对于synchronized方法,jvm自动帮我们去获取绑定该方法的对象锁了,而对于我们指定的对象,jvm会生成monitorenter和monitorexit的字节码。

可重入

        jvm编译后的monitor还有一个很重要的特性就是支持可重入。它表示,在执行monitorenter指令的时候,首先要去尝试获取对象的锁,如果这个对象没被锁定,或者当前线程已经拥有了那个对象的锁,把所的计数器加1.在执行monitorexit时将锁的计数器减1,当计数器为0时,锁就被释放了。这样一个好处就是如果当前获取到锁的线程它再次去访问到该锁锁定的部分时可以直接方法,只需要对锁计数器加1,而不至于还要被阻塞。它这种可重入性有一个典型的好处,见如下代码:

 

public class Super
{
    public synchronized void doSomething()
    {
        ...
    }
}

public class SubClass extends Super
{
    public synchronized void doSomething()
    {
        System.out.println(toString() + ": calling something");
        super.doSomething();
    }
}
 在这里,父类和子类都同步限制了doSomething方法。子类要访问父类的doSomething方法。如果锁不是可重入的话,要调用父类的方法就会被阻塞。而且,要解决这个问题也会比较麻烦。

 

lock

        java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。 ReentrantLock 类实现了Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)

        ReentrantLock类的使用方法如下所示:

Lock lock = new ReentrantLock();
lock.lock();
try { 
  // update object state
}
finally {
  lock.unlock(); 
}
        我们如果要使用ReentrantLock类,必须要用try,finally块的方式来使用,在try里面更新数据,在finally里面释放锁。

        除了上面的那些差别,ReentrantLock的加锁机制也和synchronized几乎一样,它也是可重入的,通过同样的计数器机制来获取和释放锁。

 

两者的比较

        一个非常常见的说法就是,Lock实现了synchronized的所有功能,同时提供了更加高级和更加细粒度的控制。比如ReentrantLock就有如下几项:等待可中断、可实现公平锁,以及锁可以绑定多个条件。从以往的比较来看,ReentrantLock的性能比较synchronized相对要好一些。随着JDK6以及后续一些版本的优化,他们的差别已经很小了。至少从官方来看还是比较倾向于使用synchronized。

参考资料

 

Java 理论与实践: JDK 5.0 中更灵活、更具可伸缩性的锁定机制

Thread synchronization

深入理解Java虚拟机

分享到:
评论

相关推荐

    Java并发编程实践(java concurrency in practice)pdf (java多线程总结.ppt)

    书中会讲解监视器(Monitor)、锁(Lock)、条件变量(Condition)以及它们在`synchronized`关键字中的应用。 3. **线程安全**:线程安全的类或方法能够正确处理多线程环境下的并发访问。书中讨论了线程安全的层次...

    JavaConcurrency:Java并发教程

    本教程"JavaConcurrency:Java并发教程"旨在深入探讨Java平台上的并发机制和最佳实践。 Java并发的核心概念包括线程、同步、互斥、死锁以及线程安全。在Java中,线程是并发执行的基本单元,通过创建Thread对象或者...

    Java Concurrency in Practice中文版

    《Java Concurrency in Practice》是Java并发编程领域的一本经典著作,由Brian Goetz、Tim Peierls、Joshua Bloch、Joe Bowbeer、David Holmes和Doug Lea合著,国内有热心人士进行了中文翻译,使得更多的中国开发者...

    Java-Concurrency:Java并发学习演示

    3. **线程同步**:为避免多个线程访问共享资源时产生的数据不一致问题,Java提供了多种同步机制,如`synchronized`关键字、`Lock`接口(包括`ReentrantLock`)、`Semaphore`信号量等。 4. **死锁**:当两个或更多...

    JavaConcurrency:这是一个用于Java并发包的学习程序

    Java提供了多种同步工具,包括`synchronized`关键字、`Lock`接口(如`ReentrantLock`)、`Semaphore`信号量、`CountDownLatch`倒计时器以及`CyclicBarrier`回环栅栏。`synchronized`关键字可以用于方法或代码块,...

    Java concurrency之锁_动力节点Java学院整理

    本文主要讲解了Java中的两种锁类型:同步锁和JUC(Java Util Concurrency)包中的锁。 1. 同步锁(Synchronized) 同步锁是Java 1.0就引入的原生锁机制,主要通过`synchronized`关键字实现。它的核心原则是互斥性,...

    Java Concurrency in Practice

    《Java Concurrency in Practice》是Java并发编程领域的一本经典著作,由Brian Goetz、Tim Peierls、Joshua Bloch、Joseph Bowles和Doug Lea等专家共同编写,于2006年出版。这本书深入浅出地探讨了Java平台上的多...

    Concurrency:Java多线程学习

    避免死锁的方法包括避免嵌套锁、使用定时锁(如`java.util.concurrent.locks.Lock`)和避免持有多个锁。 3. **线程通信**:Java提供了`wait()`, `notify()`和`notifyAll()`方法来实现线程间的通信。这些方法必须在`...

    Java concurrency之锁_动力节点Java学院

    本文将深入探讨Java中的锁机制,主要包括同步锁(synchronized)和Java并发工具包(Java Concurrency Utilities, JUC)中的锁。 同步锁是Java 1.0版本就引入的机制,主要通过`synchronized`关键字实现。它确保对...

    Java Concurrency in Practice CHM版本

    3. **同步机制**:Java提供了多种同步机制,包括synchronized关键字、java.util.concurrent.locks包下的Lock接口及其实现,以及java.util.concurrent.atomic包中的原子变量类。这些机制有助于控制并发访问,确保线程...

    notes-learning-java-concurrency:java 并发学习笔记

    Java并发编程学习笔记 这是我两年前(2012-05)的学习笔记。。 -- 本文不会详细介绍java5以前的多线程开发...协调共享对象访问,在java5以前用synchronized实现,现在可以用Lock显式的lock()和unlock(),并且有定时锁,读写

    Java Concurrency In Practice Learning Note

    - Lock接口与ReentrantLock类:比synchronized更灵活的锁机制,提供公平锁和非公平锁,以及可中断和可尝试获取锁的能力。 3. **并发工具类** - Executor框架:通过ExecutorService、ThreadPoolExecutor和...

    Java Concurrency in Practice.zip

    5. **同步机制**:除了`synchronized`,Java还提供了`Lock`接口及其实现,如`ReentrantLock`,提供了比`synchronized`更灵活的锁定机制,包括公平锁和非公平锁、可中断锁和定时锁。 6. **并发集合**:Java并发库...

    java-concurrency:Java 并发习惯用法的一些示例

    - `java.util.concurrent.locks.Lock`接口提供了比`synchronized`更细粒度的锁控制,如可重入锁(`ReentrantLock`)、公平锁、读写锁等。 - `ReentrantLock`支持公平性和非公平性,同时具有与`synchronized`类似的...

    Java Concurrency的一些资料

    - **Lock** 接口与实现:如`ReentrantLock`,提供了比`synchronized`更细粒度的锁控制。 4. **并发集合** - **ConcurrentHashMap**:线程安全的哈希映射,提供了高并发性能。 - **CopyOnWriteArrayList** 和 **...

    Java concurrency之互斥锁_动力节点Java学院整理

    `ReentrantLock`提供了比Java内置的`synchronized`关键字更丰富的功能和更高的控制级别。它具有可重入性,意味着一个线程可以多次获取同一锁,这在递归方法或嵌套同步块中特别有用。`ReentrantLock`有两种模式:公平...

    Java-concurrency-master.zip

    2. **线程同步**:在多线程环境下,数据共享可能导致数据不一致问题,Java提供了多种同步机制,如`synchronized`关键字、`Lock`接口(包括`ReentrantLock`)以及`volatile`关键字,以确保线程安全。 3. **并发集合*...

    concurrency:java并发

    - `Lock`接口和`ReentrantLock`类:提供比`synchronized`更细粒度的锁控制,如公平锁、非公平锁、读写锁等。 5. **并发工具类**: - `java.util.concurrent`包提供了丰富的并发工具,如`ExecutorService`、`...

    Java concurrency之Condition条件_动力节点Java学院整理

    在Java并发编程中,`Condition`接口是Java并发包(java.util.concurrent)中的一个重要组成部分,它提供了比`synchronized`关键字更为精细的线程同步和唤醒机制。`Condition`允许我们创建多个独立的等待队列,每个...

    《Java Concurrency in Practice》代码示例

    《Java Concurrency in Practice》是Java并发编程领域的一本经典著作,由Brian Goetz、Tim Peierls、Joshua Bloch、David Holmes和Doug Lea合著。这本书深入浅出地探讨了Java平台上的多线程和并发编程,提供了丰富的...

Global site tag (gtag.js) - Google Analytics