同步块

打个比方:一个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

同步买票问题

public class TicketsSystem {
 public static void main(String[] args) {
  SellThread st = new SellThread();
  Thread th1 = new Thread(st);
  th1.start();

  Thread th2 = new Thread(st);
  th2.start();

  Thread th3 = new Thread(st);
  th3.start();
 }

}

class SellThread implements Runnable {
 private int number=10;
 String s = new String();

 public void run() {
  while (number > 0) {
   synchronized (s) {
    System.out.println("第" + number + "个人在"
      + Thread.currentThread().getName() + "买票");
   }
   number--;
   try {
    Thread.sleep(10);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  }
 }
}

 

Java线程:线程的同步-同步块

 

对于同步,除了同步方法外,还可以使用同步代码块,有时候同步代码块会带来比同步方法更好的效果。

 

追其同步的根本的目的,是控制竞争资源的正确的访问,因此只要在访问竞争资源的时候保证同一时刻只能一个线程访问即可,因此Java引入了同步代码快的策略,以提高性能。

 

在上个例子的基础上,对oper方法做了改动,由同步方法改为同步代码块模式,程序的执行逻辑并没有问题。

 

 

/** 
* Java线程:线程的同步-同步代码块 

* @author leizhimin 2009-11-4 11:23:32 
*/ 
public class Test { 
        public static void main(String[] args) { 
                User u = new User("张三", 100); 
                MyThread t1 = new MyThread("线程A", u, 20); 
                MyThread t2 = new MyThread("线程B", u, -60); 
                MyThread t3 = new MyThread("线程C", u, -80); 
                MyThread t4 = new MyThread("线程D", u, -30); 
                MyThread t5 = new MyThread("线程E", u, 32); 
                MyThread t6 = new MyThread("线程F", u, 21); 

                t1.start(); 
                t2.start(); 
                t3.start(); 
                t4.start(); 
                t5.start(); 
                t6.start(); 
        } 

class MyThread extends Thread { 
        private User u; 
        private int y = 0; 

        MyThread(String name, User u, int y) { 
                super(name); 
                this.u = u; 
                this.y = y; 
        } 

        public void run() { 
                u.oper(y); 
        } 

class User { 
        private String code; 
        private int cash; 

        User(String code, int cash) { 
                this.code = code; 
                this.cash = cash; 
        } 

        public String getCode() { 
                return code; 
        } 

        public void setCode(String code) { 
                this.code = code; 
        } 

        /** 
         * 业务方法 
         * 
         * @param x 添加x万元 
         */ 
        public void oper(int x) { 
                try { 
                        Thread.sleep(10L); 
                        synchronized (this) { 
                                this.cash += x; 
                                System.out.println(Thread.currentThread().getName() + "运行结束,增加“" + x + "”,当前用户账户余额为:" + cash); 
                        } 
                        Thread.sleep(10L); 
                } catch (InterruptedException e) { 
                        e.printStackTrace(); 
                } 
        } 

        @Override 
        public String toString() { 
                return "User{" + 
                                "code='" + code + '\'' + 
                                ", cash=" + cash + 
                                '}'; 
        } 
}

线程E运行结束,增加“32”,当前用户账户余额为:132 
线程B运行结束,增加“-60”,当前用户账户余额为:72 
线程D运行结束,增加“-30”,当前用户账户余额为:42 
线程F运行结束,增加“21”,当前用户账户余额为:63 
线程C运行结束,增加“-80”,当前用户账户余额为:-17 
线程A运行结束,增加“20”,当前用户账户余额为:3 

Process finished with exit code 0

 

注意:

在使用synchronized关键字时候,应该尽可能避免在synchronized方法或synchronized块中使用sleep或者yield方法,因为synchronized程序块占有着对象锁,你休息那么其他的线程只能一边等着你醒来执行完了才能执行。不但严重影响效率,也不合逻辑。

同样,在同步程序块内调用yeild方法让出CPU资源也没有意义,因为你占用着锁,其他互斥线程还是无法访问同步程序块。当然与同步程序块无关的线程可以获得更多的执行时间。

本文出自seven的测试人生公众号最新内容请见作者的GitHub页:http://qaseven.github.io/

时间: 2024-08-20 01:42:35

同步块的相关文章

Java线程机制(四) 同步方法和同步块

在之前例子的基础上,我们增加新的功能:根据正确与不正确的响应来显示玩家的分数. public class ScoreLabel extends JLabel implements CharacterListener { private volatile int score = 0; private int char2type = -1; private CharacterSource generator = null, typist = null; public ScoreLabel(Chara

揭示同步块索引(下):总结

前面,我用两篇文章详细的讨论了同步块索引在lock和GetHashCode所起的作用.不过两篇文章是分开 来讨论的.那可能有人会问,如果我有一个object,它既作为lock的lockHelper对象,也要调用它的 GetHashCode方法该怎么办,难道这个同步块索引还可以承担这两个任务么.同步块索引是可以承担这两 个任务,但是里面却隐藏着更大的秘密,我们先来看看与同步块索引相关的结构: 大致就是这样的一个结构,一个对象的ObjectHeader中的SyncBlockIndex指向一个Sync

揭示同步块索引(中):如何获得对象的HashCode

Visual Studio + SOS 小实验 咋一看标题,觉得有些奇怪,同步块索引和HashCode有啥关系呢.从名字上来看离着十万八千里.在 不知道细节之前,我也是这样想的,知道细节之后,才发现这两兄弟如此亲密.我们还是先来用Visual Studio + SOS,看一个东西,下面是作为小白兔的示例代码: 1: using System; 2: public class Program 3: { 4: static void Main() 5: { 6: Foo f = new Foo();

揭示同步块索引(上):从lock开始

大家都知道引用类型对象除实例字段的开销外,还有两个字段的开销:类型指针和同步块索引 (SyncBlockIndex).同步块索引这个东西比起它的兄弟类型指针更少受人关注,显得有点冷落,其实此 兄功力非凡,在CLR里可谓叱咤风云,很多功能都要借助它来实现. 接下来我会用三篇来介绍同步块索引在.NET中的所作所为. 既然本章副标题是从lock开始,那我就举几个lock的示例: 代码1 public class Singleton { private static object lockHelper

Java同步块

原文链接 作者:Jakob Jenkov 译者:李同杰 Java 同步块(synchronized block)用来标记方法或者代码块是同步的.Java同步块用来避免竞争.本文介绍以下内容: Java同步关键字(synchronzied) 实例方法同步 静态方法同步 实例方法中同步块 静态方法中同步块 Java同步示例 Java 同步关键字(synchronized) Java中的同步块用synchronized标记.同步块在Java中是同步在某个对象上.所有同步在一个对象上的同步块在同时只能被

Java 并发/多线程教程(十二)-JAVA同步块

本系列译自jakob jenkov的Java并发多线程教程,个人觉得很有收获.由于个人水平有限,不对之处还望矫正! 一个Java同步块标记一个方法或一个代码块作为同步.可以使用Java同步块来避免竞态条件. java同步关键字       在Java中同步的块被标记为Synchronized关键字.Java中的同步块在某些对象上是同步的.在同一对象上同步的所有同步块只能在同一时间内执行一个线程.所有试图进入同步块的其他线程都被阻塞,直到同步块中的线程退出该块. Synchronized关键字可以

同步方法和同步块有啥区别?

问题描述 同步方法和同步块有啥区别? 解决方案 也就是线程的问题吧.用那个synchronized 修饰同一时间内只能有一个线程进入到这个方法的方法块里面,也就是加个锁,其他的线程需要等待直到占用的那个线程离开这个方法.这有一个写的挺多的.http://xiaobian.iteye.com/blog/69061

java多线程-同步块实例讲解_java

java多线程-同步块 Java 同步块(synchronized block)用来标记方法或者代码块是同步的.Java 同步块用来避免竞争.本文介绍以下内容: Java 同步关键字(synchronzied) 实例方法同步 静态方法同步 实例方法中同步块 静态方法中同步块 Java 同步示例 Java 同步关键字(synchronized) Java 中的同步块用 synchronized 标记.同步块在 Java 中是同步在某个对象上.所有同步在一个对象上的同步块在同时只能被一个线程进入并执

艾伟_转载:揭示同步块索引(上):从lock开始

大家都知道引用类型对象除实例字段的开销外,还有两个字段的开销:类型指针和同步块索引(SyncBlockIndex).同步块索引这个东西比起它的兄弟类型指针更少受人关注,显得有点冷落,其实此兄功力非凡,在CLR里可谓叱咤风云,很多功能都要借助它来实现. 接下来我会用三篇来介绍同步块索引在.NET中的所作所为. 既然本章副标题是从lock开始,那我就举几个lock的示例: 代码1 1: public class Singleton 2: { 3: private static object lock