线程同步---synchronized

public class TextThread{

 /**
  * @param args
  */
 public static void main(String[] args) {
  // TODO 自动生成方法存根
        TxtThread tt = new TxtThread();
        new Thread(tt).start();
        new Thread(tt).start();
        new Thread(tt).start();
        new Thread(tt).start();
 }

}
class TxtThread implements Runnable{
 int num = 100;
 String str = new String();
 public void run() {
  while (true){
   synchronized(str) {
   if (num>0) {
    try{
     Thread.sleep(10);
    }
    catch(Exception e)
    {
     e.getMessage();
    }
    System.out.println(Thread.currentThread().getName()+ "this is "+ num--);
   }
   }
  }
 }
}
 

上面的例子中为了制造一个时间差,也就是出错的机会,使用了 Thread.sleep(10)

 

Java 对多线程 的支持与同步机制深受大家的喜爱,似乎看起来使用了 synchronized 关键字就可以轻松地解决多线程共享数据同步问题。到底如何?――还得对 synchronized 关键字的作用进行深入了解才可定论。

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

在进一步阐述之前,我们需要明确几点:

A .无论 synchronized 关键字加在方法上还是对 象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。

B .每个对象只有 一个锁( lock )与之 相关联。

C .实现同步是要 很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

接着来讨论 synchronized 用到不同地方对代码产生的影响:

 

假设 P1 P2 是同一个类的不同对象,这个类中定义了以下几种情况的同步块或同步方法, P1 P2 就都可以调用它们。

 

1.   synchronized 当作函数修饰符时,示例代码如下:

Public synchronized void methodAAA(){
//….
}
 

这也就是同步方法,那这时 synchronized 锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象 P1 在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的 Class 所产生的另一对象 P2 却可以任意调用这个被加了 synchronized 关键字的方法。

上边的示例代码等同于如下代码:

public void methodAAA(){
synchronized (this)      //  (1)
{
       //…..
}
}
 

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

2 .同步块,示例代码如下:

  public void method3(SomeObject so) {
                     synchronized(so){
       //…..
}
}
 

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

class Foo implements Runnable{
       private byte[] lock = new byte[0];  // 特殊的instance变量
    Public void methodA(){
       synchronized(lock) { //… }
    }
//…..
}
 

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

3 .将 synchronized 作用于 static 函数,示例代码如下:

 

 Class Foo{
public synchronized static void methodAAA(){   // 同步的static 函数
//….
}

public void methodBBB()
{
       synchronized(Foo.class)   //  class literal(类名称字面常量)
}
       }

  代码 中的 methodBBB() 方法是把 class literal 作为锁的情况,它和同步的 static 函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类( Class ,而不再是由这个 Class 产生的某个具体对象了)。

记得在《 Effective Java 》一书中看到过将 Foo.class P1.getClass() 用于作同步锁还不一样,不能用 P1.getClass() 来达到锁这个 Class 的目的。 P1 指的是由 Foo 类产生的对象。

可以推断:如果一个类中定义了一个 synchronized static 函数 A , 也定义了一个 synchronized instance 函数 B , 那么这个类的同一对象 Obj 在多线程中分别访问 A B 两 个方法时,不会构成同步,因为它们的锁都不一样。 A 方法的锁是 Obj 这个对象,而 B 的锁是 Obj 所属的那个 Class

 

小结如下:

搞清楚 synchronized 锁定的是哪个对象,就能帮助我们设计更安全的多线程程序。

 

 

还有一些技巧可以让我们对共享资源的同步访问更加安全:

1.   定义 private instance 变量 + 它 的 get 方法,而不要 定义 public/protected instance 变量。如果将变量定义为 public ,对象在外界可以绕过同步方法的控制而直接取得它,并改动它。这也是 JavaBean 的标准实现方式之一。

2.   如果 instance 变量是一个对象,如数组或 ArrayList 什么的,那上述方法仍然不安全,因为当外界对象通过 get 方法拿到这个 instance 对象的引用后,又将其指向另一个对象,那么这个 private 变量也就变了,岂不是很危险。 这个时候就需要将 get 方法也加上 synchronized 同步,并且,只返回这个 private 对象的 clone() ――这样,调用端得到的就是对象副本 的引用了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值