`
Agrael
  • 浏览: 14995 次
  • 性别: Icon_minigender_1
  • 来自: 重庆
社区版块
存档分类

不要同时使用ReentrantLock类与synchronized关键字锁定会修改同一个资源的不同方法

阅读更多
    本文是讲述ReentrantLock类与synchronized关键字同时使用的问题,不是ReentrantLock类与synchronized关键字的教程。
    synchronized关键字作为java多线程编程中非常重要的关键字之一,它维护这线程并发中的安全。通常使用synchronized有2种方式。
锁定当前实例
//通过方法上使用synchronized达到锁定效果
public synchronized void xxx() {
    //...
}

//通过锁定指定的实例达到锁定效果
public void yyy(){
   synchronized (this) {
      //...
   }
}

public void zzz(){
   synchronized (xObject) {
      //...
   }
}

其中第一种和第二种都是对当前方法属于的对象实例的琐定,而第三种为锁定指定的实例。
本文不打算详细讲解synchronized关键字,有关synchronized的详细说明请参考其他资料。


java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。ReentrantLock作为Lock接口的实现,定义了可重入锁。根据API的说明:“一个可重入的互斥锁定 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁定相同的一些基本行为和语义,但功能更强大。”可以发现,ReentrantLock的最基本的作用就是实现了使用 synchronized 方法和语句所访问的隐式监视器锁定相同的一些基本行为和语义。
ReentrantLock提供的方法比较多,但这里我们只讨论实现synchronized相同功能的方式。
API中所使用的示例为:
 class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() { 
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

可以看出,如果上面代码换成synchronized的话,应该是:
   public synchronized void m() { 
       // ... method body
   }

或者
   public void m() { 
      synchronized (this) {
       // ... method body
      }
   }

锁定的是当前的实例。这也是本文的重点。(关于ReentrantLock的更多信息,请参考其他资料)
    既然ReentrantLock和synchronized都提供了相同的行为(这里不讨论性能问题),那么在使用过程中,对于线程并发编程,使用ReentrantLock与synchronized都是可以的,他们也都可以工作得很好。但是,如果同时使用它们两个呢?结果又会是怎么样呢?
看如下代码:
package cn.agrael.test.thread;

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockAndSynchronized {

	private final ReentrantLock lock = new ReentrantLock();
	
	private volatile int i = 0;
	
	public void lockAdd() throws Exception {
		lock.lock();
		try {
			check("lockAdd");
			i++;
			i++;
		} finally {
			lock.unlock();
		}
	}
	
	public synchronized void synchronizedAdd() throws Exception {
		check("synchronizedAdd");
		i++;
		i++;
	}
	
//	public void synchronizedAdd() throws Exception {
//		lock.lock();
//		try {
//			check("lockAdd");
//			i++;
//			i++;
//		} finally {
//			lock.unlock();
//		}
//	}
	
	private void check(String methodName) throws Exception {
		if (i % 2 != 0) {
			throw new Exception(methodName + " : " + i);
		}
	}
	
	public static void main(String[] args) throws Exception {
		final ReentrantLockAndSynchronized add = new ReentrantLockAndSynchronized();
		Thread thread1 = new Thread(new Runnable() {
			
			public void run() {
				try {
					while (true) {
						add.lockAdd();
					}
				} catch (Exception e) {
					e.printStackTrace();
					System.exit(0);
				}
			}
		});
		Thread thread2 = new Thread(new Runnable() {
			
			public void run() {
				try {
					while (true) {
						add.synchronizedAdd();
					}
				} catch (Exception e) {
					e.printStackTrace();
					System.exit(0);
				}
			}
		});
		thread1.start();
		thread2.start();
	}
}


其中有个int型的i变量,并提供了使用ReentrantLock锁定的lockAdd方法与使用synchronized锁定的synchronizedAdd方法,这2个方法都提供相同的操作,先验证i是否为偶数,如果不是则抛出异常,并且提供2次i++的操作。java中的i++并非原子性的操作,会涉及读和写,再者提供2次i++,如果是这样的话,会出现并发问题,所以我们提供了ReentrantLock以及synchronized来锁定,保证线程安全。如果我们的想法可行,那么i永远被读到的结果都是偶数,也就不永远不会抛我们所指定的异常。但是结果却不是这样,运行一会后,就抛出了异常,证明我们的想法失败了。因为ReentrantLock与synchronized所提供的机制不同,导致了他们是相对独立的,相当于是两把锁,各自锁定各自的。
    所以最后我们下的结论就是不要同时使用ReentrantLock类与synchronized关键字锁定会修改同一个资源的不同方法。
分享到:
评论
12 楼 弃天笑 2012-02-23  
Agrael 写道
dwbin 写道
其实就是锁的问题吧?lock所使用的锁是自己生成的,而使用关键字其实使用的是自身作为锁,两个完全不同的锁,肯定不能互相锁定的。

关键字并不是自身作为锁的。


这句如何理解?
如果关键字是加在方法那里,那么就是自身吧?
在方法的代码里直接使用,那么就是看括号里面的对象吧?

关于ReentrantLock和synchronized的区别,以及用途,楼主有没有深入研究呀
11 楼 弃天笑 2012-02-23  
刚看了一下,虽然结果是标题说的
但是实际上,两种不同的实现机制,锁的对象不同,自然也是不存在线程安全了.......
看了等于没看......
10 楼 tgs4432 2010-08-19  
<p>最近在看并发的东西,看到三种实现并发的方法时,确实遇到了楼主类似的问题,synchronized与ReentrantLock同时在一个类里面实现同样的同步动作时,并不能保证资源的互斥使用,而synchronized关键字方法和synchronized(synObject)两种形式却没问题,很想知道具体原因?</p>
9 楼 Agrael 2010-06-07  
dwbin 写道
其实就是锁的问题吧?lock所使用的锁是自己生成的,而使用关键字其实使用的是自身作为锁,两个完全不同的锁,肯定不能互相锁定的。

关键字并不是自身作为锁的。
8 楼 dwbin 2010-06-07  
其实就是锁的问题吧?lock所使用的锁是自己生成的,而使用关键字其实使用的是自身作为锁,两个完全不同的锁,肯定不能互相锁定的。
7 楼 Agrael 2010-06-07  
beneo 写道
Agrael 写道
beneo 写道
ReentrantLock synchronized 又没有使用同一个monitor,当然没有线程安全的效果

这文章post出来我第一个感觉这就是标题党

你何不研究一下 volatile ReentrantLock synchronized 这三者的区别与联系。


你的回答就是我过程中所证明的东西,不是吗?这个标题也是最后的结论,何来标题党一说?
我想在实际编程中也会有人遇到这2种机制同时使用的疑问吧?
volatile ReentrantLock synchronized 这三者的区别与联系,区别已经有很多资料都有讲。这篇文章就我个人觉得,涉及了一些他们的联系的问题,不是吗?


你的做法很值得提倡,但是你文章的技术含量不是很高,这点你应该同意吧。

你说这三者联系的文章很多,但是其实真的没有那么多,Java的锁机制本来就不是那么简单就能搞清楚的,就能用中文表达出来的更是少之又少。如果你肯再往里面专研,post出一片更加优质的文章,肯定不会有那么多人认为你这篇文章是新手帖的。

我说你是标题党,因为我看过你前几次贴的帖子,感觉都很不错,这次我看到标题,怀着很大的希望进来,结果……


是的,这篇帖子的技术含量并不高,因为这篇帖子最初我的定位没定位为一个技术探讨帖,我通过这篇帖子想表达的是给写程序的朋友们说明那么一个结果,这篇帖子也只是这么一个过程,并没有什么太大的技术含量。
Java的锁机制本来就不是那么简单就能搞清楚的这点我非常同意。其实我也有想,如果在这个过程中套入大量的原理在里面,可能这篇文章的质量是上去了,但是我想给尽可能对的人说明这个结果的初衷可能就实现不了了。越是复杂,理解的人就越少,而结论通常是可以用的,我是这样想的。
PS:谢谢你关注我的帖子~:)
6 楼 beneo 2010-06-07  
Agrael 写道
beneo 写道
ReentrantLock synchronized 又没有使用同一个monitor,当然没有线程安全的效果

这文章post出来我第一个感觉这就是标题党

你何不研究一下 volatile ReentrantLock synchronized 这三者的区别与联系。


你的回答就是我过程中所证明的东西,不是吗?这个标题也是最后的结论,何来标题党一说?
我想在实际编程中也会有人遇到这2种机制同时使用的疑问吧?
volatile ReentrantLock synchronized 这三者的区别与联系,区别已经有很多资料都有讲。这篇文章就我个人觉得,涉及了一些他们的联系的问题,不是吗?


你的做法很值得提倡,但是你文章的技术含量不是很高,这点你应该同意吧。

你说这三者联系的文章很多,但是其实真的没有那么多,Java的锁机制本来就不是那么简单就能搞清楚的,就能用中文表达出来的更是少之又少。如果你肯再往里面专研,post出一片更加优质的文章,肯定不会有那么多人认为你这篇文章是新手帖的。

我说你是标题党,因为我看过你前几次贴的帖子,感觉都很不错,这次我看到标题,怀着很大的希望进来,结果……
5 楼 Agrael 2010-06-07  
beneo 写道
ReentrantLock synchronized 又没有使用同一个monitor,当然没有线程安全的效果

这文章post出来我第一个感觉这就是标题党

你何不研究一下 volatile ReentrantLock synchronized 这三者的区别与联系。


你的回答就是我过程中所证明的东西,不是吗?这个标题也是最后的结论,何来标题党一说?
我想在实际编程中也会有人遇到这2种机制同时使用的疑问吧?
volatile ReentrantLock synchronized 这三者的区别与联系,区别已经有很多资料都有讲。这篇文章就我个人觉得,涉及了一些他们的联系的问题,不是吗?
4 楼 beneo 2010-06-07  
ReentrantLock synchronized 又没有使用同一个monitor,当然没有线程安全的效果

这文章post出来我第一个感觉这就是标题党

你何不研究一下 volatile ReentrantLock synchronized 这三者的区别与联系。

3 楼 Agrael 2010-06-07  
针对一楼的我补充下,ReentrantLock保证同步修改的同时,如果使得变量可见,是没问题的,并不需要锁定变量i。
而且,如果是synchronized,他自己也会保证可见性。

PS:这些都是并发编程中常见到的问题,如果是一个人编写,可能不会存在什么问题,如果是不同的人编写,就要注意我文中所说的了。
2 楼 Agrael 2010-06-07  
kimmking 写道
你锁了方法的执行,又没有锁变量 i

因为修改是在方法中执行的。
1 楼 kimmking 2010-06-07  
你锁了方法的执行,又没有锁变量 i

相关推荐

    ReentrantLock与synchronized

    - 当一个线程进入某个对象的`synchronized`方法或代码块时,其他线程必须等待该线程退出后才能获得锁并执行相应的方法或代码块。 2. **使用方式**: - 修饰实例方法:锁定的是当前实例对象。 - 修饰静态方法:...

    synchronized关键字的实质及用法

    《深入理解Java中的synchronized关键字》 在Java编程语言中,`synchronized`关键字是用于实现线程同步的重要工具,它的本质在于确保多线程环境下的数据一致性与安全性。通过`synchronized`,我们可以控制对共享资源...

    【ASP.NET编程知识】实例解析Java中的synchronized关键字与线程安全问题.docx

    `print1` 方法是一个同步方法,但并未实现预期的线程安全性,因为每个`MyThread` 实例有自己的锁,所以不同的线程实例可以同时执行 `print1`,导致输出混合。而 `print2` 方法通过同步类锁 `MyThread.class` 实现了...

    Java并发-Synchronized关键字.docx

    本文将探讨Synchronized关键字在解决并发控制中的作用及其使用方式。 首先,让我们回顾一下问题的背景:在给出的示例代码中,创建了10个线程,每个线程都对共享变量`count`进行10000000次的累加操作。理论上,最终...

    Lock、Synchoronized和ReentrantLock的使用

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

    Java多线程synchronized关键字详解(六)共5

    `synchronized`基于Java虚拟机(JVM)的监视器锁(Monitor)机制,当一个线程进入同步块或同步方法时,会获取到对应的锁,其他试图进入的线程会被阻塞,直到持有锁的线程释放锁。 4. **锁升级与优化** - Java内存...

    并发编程原理学习:synchronized关键字.doc

    `synchronized`关键字可以用于修饰方法或作为同步代码块,其核心目标是保证线程对共享资源的访问具有互斥性和可见性,防止数据不一致和竞态条件。 ### 同步代码块 同步代码块的形式如下: ```java synchronized ...

    深入java并发编程,使用ReentrantLock和 Synchronized加锁

    在Java并发编程中,理解和熟练使用同步机制是至关重要的,这包括了`ReentrantLock`和`synchronized`关键字。这两个工具都是为了确保多线程环境中的数据一致性与安全性,防止出现竞态条件和死锁等问题。 `...

    Java多线程同步.pdf

    在Java语言中,有两个特殊的类用于多线程同步,分别是synchronized关键字和ReentrantLock类。 1. synchronized关键字 synchronized关键字是Java语言中的一种同步机制,用于对方法或者代码块进行同步。synchronized...

    java关键字Synchronized详解

    Java中的`synchronized`关键字是实现线程同步的关键机制,它保证了在多线程环境下,对共享资源的访问是互斥的,防止了数据竞争和不一致的问题。synchronized通过锁的概念来控制对代码块或方法的访问,分为两种形式:...

    Java synchronized使用案例

    例如,创建一个共享资源类,然后通过多个线程去操作这个资源,使用`synchronized`关键字来保证线程安全。实验可能涉及线程并发访问计数器、银行转账等场景。 总的来说,理解并合理使用`synchronized`对于编写高效、...

    使用synchronized实现多线程同步[借鉴].pdf

    在这个例子中,`synchronized`代码块锁定的是`lock`对象,这意味着即使有多个`ThreadTest`实例,只要它们使用的锁对象不同,也可以并发执行`run()`方法。 4. **线程同步的目的**: 线程同步的主要目的是保护共享...

    Java 多线程synchronized关键字详解(六)

    如果不使用`synchronized`关键字,像例子程序1那样,两个线程`t1`和`t2`可能会同时执行`execute()`方法,导致并发输出。而当添加`synchronized`后,线程将按照一定的顺序执行,确保线程安全。 2. **多个同步方法**...

    java synchronized demo

    - 死锁是两个或多个线程相互等待对方释放资源导致的僵局,`synchronized`如果不恰当使用可能会引发死锁,但Java提供了更高级的并发工具,如`ReentrantLock`,可以更好地控制和避免死锁。 - 活锁则是线程不断地尝试...

    并发编程 70 道面试题及答案.docx

    * synchronized关键字:使用synchronized关键字可以实现线程同步,确保只有一个线程可以访问共享资源。 * volatile关键字:volatile关键字可以确保变量的可见性,禁止指令重排序优化,保证了多线程环境下的数据一致...

    synchronized并发讲解源码.zip

    `synchronized`还有一种高级用法——同步类方法(静态方法),这会锁定整个类,而非单个对象: ```java public class MyClass { public static synchronized void method() { // ... } } ``` 在这种情况下,所有...

    Java多线程并发编程 Synchronized关键字

    在上述示例中,`Test`类的`count`方法被`synchronized`修饰,因此线程A和线程B在尝试调用`count`时,只有一个线程能获得锁并执行,另一个线程则必须等待。 2. **原子性保证**: `synchronized`关键字确保了在同一...

    Java使用synchronized修饰方法来同步线程的实例演示

    synchronized关键字确保了在给定的同步代码块或方法内,只有一个线程可以执行,从而避免了这类问题。 5. **注意点** - 虽然synchronized提供了线程安全,但它也会带来性能开销,因为线程需要等待锁的释放。因此,...

    java多线程编程之Synchronized关键字详解

    在上述讨论中,`synchronized`锁的范围是整个对象,这意味着如果类中有多个`synchronized`方法,且多个线程持有该类的同一个实例,那么这些方法的执行将是同步的。这种全局的同步可能会降低程序的并发性能,尤其是在...

    java基本教程之synchronized关键字 java多线程教程

    - **规则三**:对于同一个对象,一个线程在执行synchronized方法或代码块时,其他线程对同一对象的其他synchronized方法或代码块的访问也会被阻塞。 3. **synchronized方法和代码块** - **synchronized方法**:当...

Global site tag (gtag.js) - Google Analytics