`

详析同步synchronized方法和代码块

    博客分类:
  • Java
 
阅读更多

打个比方:一个object就像一个大房子,大门永远打开。房子里有很多房间(也就是方法)。这些房间有上锁的(synchronized方法),和不上锁之分(普通方法)。房门口放着一把钥匙(key),这把钥匙可以打开所有上锁的房间。另外我把所有想调用该对象方法的线程比喻成想进入这房子某个房间的人。所有的东西就这么多了,下面我们看看这些东西之间如何作用的。 

在此我们先来明确一下我们的前提条件。该对象至少有一个synchronized方法,否则这个key还有啥意义。当然也就不会有我们的这个主题了。 

一个人想进入某间上了锁的房间,他来到房子门口,看见钥匙在那儿(说明暂时还没有其他人要使用上锁的房间)。于是他走上去拿到了钥匙,并且按照自己的计划使用那些房间。注意一点,他每次使用完一次上锁的房间后会马上把钥匙还回去。即使他要连续使用两间上锁的房间,中间他也要把钥匙还回去,再取回来。 

因此,普通情况下钥匙的使用原则是:“随用随借,用完即还。” 

这时其他人可以不受限制的使用那些不上锁的房间,一个人用一间可以,两个人用一间也可以,没限制。但是如果当某个人想要进入上锁的房间,他就要跑到大门口去看看了。有钥匙当然拿了就走,没有的话,就只能等了。 

要是很多人在等这把钥匙,等钥匙还回来以后,谁会优先得到钥匙?Not guaranteed。象前面例子里那个想连续使用两个上锁房间的家伙,他中间还钥匙的时候如果还有其他人在等钥匙,那么没有任何保证这家伙能再次拿到。(JAVA规范在很多地方都明确说明不保证,象Thread.sleep()休息后多久会返回运行,相同优先权的线程那个首先被执行,当要访问对象的锁被释放后处于等待池的多个线程哪个会优先得到,等等。我想最终的决定权是在JVM,之所以不保证,就是因为JVM在做出上述决定的时候,绝不是简简单单根据一个条件来做出判断,而是根据很多条。而由于判断条件太多,如果说出来可能会影响JAVA的推广,也可能是因为知识产权保护的原因吧。SUN给了个不保证就混过去了。无可厚非。但我相信这些不确定,并非完全不确定。因为计算机这东西本身就是按指令运行的。即使看起来很随机的现象,其实都是有规律可寻。学过计算机的都知道,计算机里随机数的学名是伪随机数,是人运用一定的方法写出来的,看上去随机罢了。另外,或许是因为要想弄的确定太费事,也没多大意义,所以不确定就不确定了吧。) 

再来看看同步代码块。和同步方法有小小的不同。 

1.从尺寸上讲,同步代码块比同步方法小。你可以把同步代码块看成是没上锁房间里的一块用带锁的屏风隔开的空间。 

2.同步代码块还可以人为的指定获得某个其它对象的key。就像是指定用哪一把钥匙才能开这个屏风的锁,你可以用本房的钥匙;你也可以指定用另一个房子的钥匙才能开,这样的话,你要跑到另一栋房子那儿把那个钥匙拿来,并用那个房子的钥匙来打开这个房子的带锁的屏风。 

记住你获得的那另一栋房子的钥匙,并不影响其他人进入那栋房子没有锁的房间。 

为什么要使用同步代码块呢?我想应该是这样的:首先对程序来讲同步的部分很影响运行效率,而一个方法通常是先创建一些局部变量,再对这些变量做一些操作,如运算,显示等等;而同步所覆盖的代码越多,对效率的影响就越严重。因此我们通常尽量缩小其影响范围。如何做?同步代码块。我们只把一个方法中该同步的地方同步,比如运算。

另外,同步代码块可以指定钥匙这一特点有个额外的好处,是可以在一定时期内霸占某个对象的key。还记得前面说过普通情况下钥匙的使用原则吗。现在不是普通情况了。你所取得的那把钥匙不是永远不还,而是在退出同步代码块时才还。 

还用前面那个想连续用两个上锁房间的家伙打比方。怎样才能在用完一间以后,继续使用另一间呢。用同步代码块吧。先创建另外一个线程,做一个同步代码块,把那个代码块的锁指向这个房子的钥匙。然后启动那个线程。只要你能在进入那个代码块时抓到这房子的钥匙,你就可以一直保留到退出那个代码块。也就是说你甚至可以对本房内所有上锁的房间遍历,甚至再sleep(10*60*1000),而房门口却还有1000个线程在等这把钥匙呢。很过瘾吧。 

在此对sleep()方法和钥匙的关联性讲一下。一个线程在拿到key后,且没有完成同步的内容时,如果被强制sleep()了,那key还一直在它那儿。直到它再次运行,做完所有同步内容,才会归还key。记住,那家伙只是干活干累了,去休息一下,他并没干完他要干的事。为了避免别人进入那个房间把里面搞的一团糟,即使在睡觉的时候他也要把那唯一的钥匙戴在身上。 

最后,也许有人会问,为什么要一把钥匙通开,而不是一个钥匙一个门呢?我想这纯粹是因为复杂性问题。一个钥匙一个门当然更安全,但是会牵扯好多问题。钥匙的产生,保管,获得,归还等等。其复杂性有可能随同步方法的增加呈几何级数增加,严重影响效率。 

这也算是一个权衡的问题吧。为了增加一点点安全性,导致效率大大降低,是多么不可取啊。 

摘自:http://www.54bk.com/more.asp?name=czp&id=2097 

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

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

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

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

五、以上规则对其它对象锁同样适用. 

举例说明: 

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

package ths; 

public class Thread1 implements Runnable { 
public void run() { 
synchronized(this) { 
for (int i = 0; i < 5; i++) { 
System.out.println(Thread.currentThread().getName() + " synchronized loop " + i); 



public static void main(String[] args) { 
Thread1 t1 = new Thread1(); 
Thread ta = new Thread(t1, "A"); 
Thread tb = new Thread(t1, "B"); 
ta.start(); 
tb.start(); 



结果: 

A synchronized loop 0 
A synchronized loop 1 
A synchronized loop 2 
A synchronized loop 3 
A synchronized loop 4 
B synchronized loop 0 
B synchronized loop 1 
B synchronized loop 2 
B synchronized loop 3 
B synchronized loop 4 

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

package ths; 

public class Thread2 { 
public void m4t1() { 
synchronized(this) { 
int i = 5; 
while( i-- > 0) { 
System.out.println(Thread.currentThread().getName() + " : " + i); 
try { 
Thread.sleep(500); 
} catch (InterruptedException ie) { 




public void m4t2() { 
int i = 5; 
while( i-- > 0) { 
System.out.println(Thread.currentThread().getName() + " : " + i); 
try { 
Thread.sleep(500); 
} catch (InterruptedException ie) { 



public static void main(String[] args) { 
final Thread2 myt2 = new Thread2(); 
Thread t1 = new Thread( 
new Runnable() { 
public void run() { 
myt2.m4t1(); 

}, "t1" 
); 
Thread t2 = new Thread( 
new Runnable() { 
public void run() { 
myt2.m4t2(); 

}, "t2" 
); 
t1.start(); 
t2.start(); 



结果: 

t1 : 4 
t2 : 4 
t1 : 3 
t2 : 3 
t1 : 2 
t2 : 2 
t1 : 1 
t2 : 1 
t1 : 0 
t2 : 0 

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

//修改Thread2.m4t2()方法: 

public void m4t2() { 
synchronized(this) { 
int i = 5; 
while( i-- > 0) { 
System.out.println(Thread.currentThread().getName() + " : " + i); 
try { 
Thread.sleep(500); 
} catch (InterruptedException ie) { 






结果: 

t1 : 4 
t1 : 3 
t1 : 2 
t1 : 1 
t1 : 0 
t2 : 4 
t2 : 3 
t2 : 2 
t2 : 1 
t2 : 0 

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

//修改Thread2.m4t2()方法如下: 

public synchronized void m4t2() { 
int i = 5; 
while( i-- > 0) { 
System.out.println(Thread.currentThread().getName() + " : " + i); 
try { 
Thread.sleep(500); 
} catch (InterruptedException ie) { 




结果: 

t1 : 4 
t1 : 3 
t1 : 2 
t1 : 1 
t1 : 0 
t2 : 4 
t2 : 3 
t2 : 2 
t2 : 1 
t2 : 0 

五、以上规则对其它对象锁同样适用: 

package ths; 

public class Thread3 { 
class Inner { 
private void m4t1() { 
int i = 5; 
while(i-- > 0) { 
System.out.println(Thread.currentThread().getName() + " : Inner.m4t1()=" + i); 
try { 
Thread.sleep(500); 
} catch(InterruptedException ie) { 



private void m4t2() { 
int i = 5; 
while(i-- > 0) { 
System.out.println(Thread.currentThread().getName() + " : Inner.m4t2()=" + i); 
try { 
Thread.sleep(500); 
} catch(InterruptedException ie) { 




private void m4t1(Inner inner) { 
synchronized(inner) { //使用对象锁 
inner.m4t1(); 


private void m4t2(Inner inner) { 
inner.m4t2(); 

public static void main(String[] args) { 
final Thread3 myt3 = new Thread3(); 
final Inner inner = myt3.new Inner(); 
Thread t1 = new Thread( 
new Runnable() { 
public void run() { 
myt3.m4t1(inner); 

}, "t1" 
); 
Thread t2 = new Thread( 
new Runnable() { 
public void run() { 
myt3.m4t2(inner); 

}, "t2" 
); 
t1.start(); 
t2.start(); 



结果: 

尽管线程t1获得了对Inner的对象锁,但由于线程t2访问的是同一个Inner中的非同步部分。所以两个线程互不干扰。 

t1 : Inner.m4t1()=4 
t2 : Inner.m4t2()=4 
t1 : Inner.m4t1()=3 
t2 : Inner.m4t2()=3 
t1 : Inner.m4t1()=2 
t2 : Inner.m4t2()=2 
t1 : Inner.m4t1()=1 
t2 : Inner.m4t2()=1 
t1 : Inner.m4t1()=0 
t2 : Inner.m4t2()=0 

现在在Inner.m4t2()前面加上synchronized: 


private synchronized void m4t2() { 
int i = 5; 
while(i-- > 0) { 
System.out.println(Thread.currentThread().getName() + " : Inner.m4t2()=" + i); 
try { 
Thread.sleep(500); 
} catch(InterruptedException ie) { 




结果: 

尽管线程t1与t2访问了同一个Inner对象中两个毫不相关的部分,但因为t1先获得了对Inner的对象锁,所以t2对Inner.m4t2()的访问也被阻塞,因为m4t2()是Inner中的一个同步方法。 

t1 : Inner.m4t1()=4 
t1 : Inner.m4t1()=3 
t1 : Inner.m4t1()=2 
t1 : Inner.m4t1()=1 
t1 : Inner.m4t1()=0 
t2 : Inner.m4t2()=4 
t2 : Inner.m4t2()=3 
t2 : Inner.m4t2()=2 
t2 : Inner.m4t2()=1 
t2 : Inner.m4t2()=0

分享到:
评论

相关推荐

    MP3源代码详析 MP3源代码详析

    MP3源代码详析MP3源代码详析MP3源代码详析MP3源代码详析MP3源代码详析MP3源代码详析MP3源代码详析MP3源代码详析MP3源代码详析MP3源代码详析MP3源代码详析MP3源代码详析MP3源代码详析MP3源代码详析MP3源代码详析MP3源...

    详析模式方法及其在教育科学研究中的应用

    ### 详析模式方法及其在教育科学研究中的应用 #### 一、教育研究中因果分析的困境呼唤详析模式方法 教育研究的对象主要是人及其相关的社会现象,这些对象本身具有高度的复杂性和不确定性。例如,教育成就与性别...

    操作系统中的进程同步详析

    本篇文章将基于提供的示例代码深入剖析进程同步的基本概念、实现方法以及应用场景。 #### 进程同步概述 进程同步主要解决的是当多个进程共享某些资源时如何避免冲突的问题。例如,两个或更多的进程可能都需要访问...

    实例详析Linux文件同步机制.pdf

    在Linux操作系统中,文件同步是一个至关重要的任务,它涉及到数据的安全性和一致性。传统的Rsync工具虽然能实现数据的备份和同步,但存在一些不足。Rsync在同步过程中会对比所有文件,这在处理大量文件时既耗时又...

    SCJP试题详析(PDF)

    8. **多线程**:掌握线程的创建方式(继承Thread类和实现Runnable接口),线程同步机制(synchronized关键字、wait/notify、Lock接口等),以及并发工具类如Semaphore、CountDownLatch等。 9. **反射**:理解Java...

    SCJP试题详析 .

    理解线程的创建、同步和通信方法,以及死锁和活锁的概念,对于编写并发程序至关重要。 9. **反射和注解**:反射允许在运行时检查和修改类、接口、字段和方法。注解提供了一种元数据的方式,可以用于编译时或运行时...

    SCJP---试题详析

    6. **多线程**:学习如何创建和管理线程,同步机制(如synchronized关键字、wait()、notify()、notifyAll()方法),以及线程池的使用。 7. **输入/输出流**:掌握I/O流的基本概念,包括文件流、字符流、字节流,...

    Sun Java 认证SCJP试题详析

    6. **多线程**:讲述了线程的创建、同步机制(synchronized关键字、wait/notify方法、Lock接口等),以及并发编程的相关概念。多线程是构建高效应用程序的关键。 7. **输入输出流**:介绍了文件操作、缓冲流、对象...

    SCJP试题详析(中文)

    2. **同步机制**:synchronized关键字、wait(), notify(), notifyAll()方法,以及死锁和活锁的概念。 七、反射和注解 1. **反射**:在运行时动态获取类信息和对象信息,创建对象,调用方法,访问字段。 2. **注解**...

    SCJP试题详析.pdf

    多线程是Java的一大特色,书中会涵盖线程的创建、同步机制(如synchronized关键字和wait/notify机制)、线程池等主题。通过学习这部分内容,考生可以掌握编写并发程序的基本技巧,理解线程间的交互和通信。 集合...

    scjp试题详析.pdf

    scjp 试题中文讲解,各个章节对应java知识点,并有相关试题练习

    大型3D游戏设计制作详析及源代码,游戏脚本,设计报告,技术报告,用户手册.zip

    首先,"大型3D游戏设计制作详析"是一份详细的教学文档,它解释了如何构思、规划和实现一个大型3D游戏项目。内容可能包括但不限于游戏世界观构建、角色设定、关卡设计、游戏机制设计以及游戏引擎的选择和应用。设计...

    ASP-NET考试试题及答案详析.doc

    ASP-NET 考试试题及答案详析,涵盖了 ASP-NET 的各种知识点,包括 ASPX 网页的代码储存模式、Web 控件的事件处理、TextBox 控件的使用、Response.Redirect 方法、验证控件的使用、Session 对象的设置和取出、...

    python中时间转换datetime和pd.to_datetime详析

    本文将深入探讨两种常用的时间转换方法:`datetime` 和 `pd.to_datetime`。这两种方法都是为了将不同格式的时间数据转换成标准的日期时间对象,以便进行进一步的操作。 首先,我们来看`datetime` 模块。`datetime` ...

    大型3D游戏设计制作详析及源代码,游戏脚本,设计报告,技术报告,用户手册.rar

    本资源包"大型3D游戏设计制作详析及源代码,游戏脚本,设计报告,技术报告,用户手册.rar"为我们揭示了这一复杂过程的冰山一角。下面,我们将深入探讨其中涉及的关键知识点。 1. **3D图形学基础**:3D游戏的核心是3D...

    Java编程细节重构之为什么if-else不是好代码详析

    这样,我们可以将代码块中不同条件下的方法抽到各个不同的具体类里面去了,进一步优化代码的结构。 在第二种情况下,我们可以定义一个工厂来把new ExecutorWithTag()这件事给包了,这样,我们可以将条件判断都不要...

    Android Handler机制的工作原理详析

    Android Handler机制的工作原理详析 Android Handler机制是Android系统中的一种消息机制,负责处理应用程序中的消息处理和线程之间的通信。Handler机制的工作原理可以分为以下几个部分:Looper、MessageQueue、...

    十多变量分析详析模型与多元线性回归.ppt

    【标题】:“十多变量分析详析模型与多元线性回归.ppt”主要涉及的是多变量统计分析中的详析分析和多元线性回归方法。这两者是统计学中用于理解和建模复杂数据关系的重要工具。 详析分析(Exploratory Analysis)是...

Global site tag (gtag.js) - Google Analytics