返回顶部

多线程学习-基础( 九)线程同步Synchronized关键字

一、线程同步
1、synchronized关键字的作用域有二种:
(1)某个对象实例内:synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中一个synchronized方法,其他线程不能同时访问这个对象的任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相互干扰的。也就是说:其他线程照样可以访问同一个类的不同实例中的synchronized方法。
(2)是某个类的范围:synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。

2、除了方法前使用synchronized关键字,还可以用于方法中的某个区块中,表示这个区块的资源是互斥访问的,用法是:
synchronized(this){/代码块/}
它的作用域是当前对象。
3、synchronized关键字是不能继承的,也就是说,基类中的synchronized fun(),在继承类中并不是自动的synchronized fun(),而是变成了 fun(),继承需要你显示地指定它的某个方法为synchronized 方法。


总的来说,synchronized关键字可以作为函数的修饰符,也可以作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细致的分类,synchronized关键字可用于instence变量,object reference(对象引用),static函数和class literals(类名称字面常量)身上。

在进一步阐述之前,我们需要明确以下几点:
A:无论synchronized关键字加在方法还是对象上,它取得锁都是对象,而不是把一段代码或者函数当做锁,而且同步方法还可能被其他线程的对象访问,(说明:其他线程的对象是指:和当前对象是同一个类 但是却不是同一个实例)。
B:每个对象只有一把锁与之相关联。
C:实现同步是需要很大的系统开销作为代价的,甚至可能造成死锁,所以尽可能编码无所谓的同步控制。

接下来分析下:synchronized关键字用在不同的地方队代码产生的影响:
假设:P1、P2是同一个类的不同对象,这个类定义了以下几种情况的同步块或同步方法,P1、P2都可以调用它们。
1、把synchronized关键字当做函数修饰符使用时,代码如下:

1 public synchronized void method_A(){
2     //......
3 }

   这就是同步方法,那这个时候,synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法的对象。也就是说当一个对象P1在不同的线程中执行这个同步方法时,它们之间会互斥,达到同步的效果。但是这个对象所属的class类的其他实例对象,比如P2却可以随意调用这个被synchronized修饰的同步方法。根本原因:
synchroonized关键字修饰方法时,这个方法被叫做同步方法,同步的方法锁住的对象是,同步方法所在类的那个实例化对象,但是类的实例化可以有无数个,那么到底锁住的是哪个呢?答案是当前调用synchronized修饰方法的那个对象。
由于每个对象都会被JVM分配一把锁,一个类有多个实例化对象,同一个对象可以被多个线程调用,正在被锁的是“当前线程队当前这个对象的所有synchronized方法或synchronized块的使用权限”。一旦某个线程正在使用,其他线只能处于排队等待状态,而不可使用。
上边的示例代码等同于如下代码:

1 public void method_A()
2 {
3      synchronized (this) // synchronized同步块
4      {
5            //…..
6      }
7 }

 


分析上面的代码:
synchronized代码块中:this指的是什么?它指的就是调用这个方法的对象,如P1。可见同步方法实质是将synchronized作用于object reference。――那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱:

2、同步代码块,实例代码如下:

1 public void method3(SomeObject so) {
2   synchronized(so)
3   {
4     //…..同步代码块
5   }
6 }

 

 

         这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:

1 class Foo implements Runnable
2 {
3     private byte[] lock = new byte[0]; // 特殊的instance变量
4     public void methodA()
5     {
6                 synchronized(lock) { //… }
7     }
8      //…..
9 }

 


注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。
3、将synchronized作用于static 函数,示例代码如下:

 1 Class Foo
 2 {
 3   public synchronized static void methodAAA() // 同步的static 函数
 4   {
 5   //….
 6   }
 7   public void methodBBB()
 8   {
 9     synchronized(Foo.class) // class literal(类名称字面常量)
10   }
11 }

 


代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。
可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。B方法的锁是Obj这个对象,而A的锁是Obj所属的那个Class。
总结一下:
1、线程同步的目的是为了保护多个线程访问一个资源时对资源的破坏。
2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他非同步方法。
3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
4、对于同步,要时刻清醒在哪个对象上同步,这是关键。
5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。
6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。

 

posted @ 2018-04-23 22:18  小风微灵-彦  阅读(389)  评论(0编辑  收藏  举报
加载中……