不久前用到了同步,现在回过头来对JAVA中的同步做个总结,以对前段时间工作的总结和自我技术的条理话。JAVA中synchronized关键字能够作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。假如再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。 


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

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

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

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

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


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


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

Public synchronized void method(){ 

//…. 


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

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

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

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

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

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

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

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

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

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

 

Class Foo 
 { 
 public synchronized static void method1()   // 同步的static 函数 
 { 
 //…. 
 } 
 public void method2() 
 { 
        synchronized(Foo.class)   //  class literal(类名称字面常量) 
 } 
        }

   代码中的method2()方法是把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所属的那个Class,而B的锁是Obj所属的这个对象。 


小结如下: 

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



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

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


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


百度百科

概述

  synchronized 

关键字,代表这个方法加锁,相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程B(或者C、 D等)正在用这个方法,有的话要等正在使用synchronized方法的线程B(或者C 、D)运行完这个方法后再运行此线程A,没有的话,直接运行。它包括两种用法:synchronized 方法和 synchronized 块。

1. synchronized 方法:

  声明是为了定义变量的作用范围和作用域

  通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。如:

  public synchronized void accessVal(int newVal);

  synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)。

  在 Java 中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为 synchronized ,以控制其对类的

静态成员变量的访问。

  synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率,典型地,若将线程类的方法 run() 声明为 synchronized ,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何 synchronized 方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为 synchronized ,并在主方法中调用来解决这一问题,但是 Java 为我们提供了更好的解决办法,那就是 synchronized 块。

2. synchronized 块:

  通过 synchronized关键字来声明synchronized 块。语法如下:

  synchronized(syncObject) {

  //允许访问控制的代码

  }

  synchronized 块是这样一个代码块,其中的代码必须获得对象 syncObject (如前所述,可以是类实例或类)的锁方能执行,具体机制同前所述。由于可以针对任意代码块,且可任意指定上锁的对象,故灵活性较高。


编辑本段对synchronized(this)的一些理解

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

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

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

  四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的

对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。

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


编辑本段synchronized的4种用法

1.方法声明时使用,放在范围操作符(public等)之后,返回类型声明(void等)之前。即一次只能有一个线程进入该方法,其他线程要想在此时调用该方法,只能排队等候,当前线程(就是在synchronized方法内部的线程)执行完该方法后,别的线程才能进入。 

例如: 

public synchronized void synMethod() { 
 //方法体 
 } 
 2.对某一代码块使用,synchronized后跟括号,括号里是变量,这样,一次只有一个线程进入该代码块。例如: 
 public int synMethod(int a1){ 
 synchronized(a1) { 
 //一次只能有一个线程进入 
 } 
 } 
 3.synchronized后面括号里是一对象,此时,线程获得的是对象锁。例如: 
 public class MyThread implements Runnable { 
 public static void main(String args[]) { 
 MyThread mt = new MyThread(); 
 Thread t1 = new Thread(mt, "t1"); 
 Thread t2 = new Thread(mt, "t2"); 
 Thread t3 = new Thread(mt, "t3"); 
 Thread t4 = new Thread(mt, "t4"); 
 Thread t5 = new Thread(mt, "t5"); 
 Thread t6 = new Thread(mt, "t6"); 
 t1.start(); 
 t2.start(); 
 t3.start(); 
 t4.start(); 
 t5.start(); 
 t6.start(); 
 } 
 public void run() { 
 synchronized (this) { 
 System.out.println(Thread.currentThread().getName()); 
 } 
 } 
 }

对于3,如果线程进入,则得到对象锁,那么别的线程在该类所有对象上的任何操作都不能进行。在对象级使用锁通常是一种比较粗糙的方法。为什么要将整个对象都上锁,而不允许其他线程短暂地使用对象中其他同步方法来访问共享资源?如果一个对象拥有多个资源,就不需要只为了让一个线程使用其中一部分资源,就将所有线程都锁在外面。由于每个对象都有锁,可以如下所示使用虚拟对象来上锁: 

class FineGrainLock { 
 MyMemberClass x, y; 
 Object xlock = new Object(), ylock = new Object(); 
 public void foo() { 
 synchronized(xlock) { 
 //access x here 
 } 
 //do something here - but don‘t use shared resources 
 synchronized(ylock) { 
 //access y here 
 } 
 } 
 public void bar() { 
 synchronized(this) { 
 //access both x and y here 
 } 
 //do something here - but don‘t use shared resources 
 } 
 } 
 4.synchronized后面括号里是类。例如: 
 class ArrayWithLockOrder{ 
 private static long num_locks = 0; 
 private long lock_order; 
 private int[] arr; 
 public ArrayWithLockOrder(int[] a) 
 { 
 arr = a; 
 synchronized(ArrayWithLockOrder.class) {//-----------------------------------------这里 
 num_locks++; // 锁数加 1。 
 lock_order = num_locks; // 为此对象实例设置唯一的 lock_order。 
 } 
 } 
 public long lockOrder() 
 { 
 return lock_order; 
 } 
 public int[] array() 
 { 
 return arr; 
 } 
 } 
 class SomeClass implements Runnable 
 { 
 public int sumArrays(ArrayWithLockOrder a1, 
 ArrayWithLockOrder a2) 
 { 
 int value = 0; 
 ArrayWithLockOrder first = a1; // 保留数组引用的一个 
 ArrayWithLockOrder last = a2; // 本地副本。 
 int size = a1.array().length; 
 if (size == a2.array().length) 
 { 
 if (a1.lockOrder() > a2.lockOrder()) // 确定并设置对象的锁定 
 { // 顺序。 
 first = a2; 
 last = a1; 
 } 
 synchronized(first) { // 按正确的顺序锁定对象。 
 synchronized(last) { 
 int[] arr1 = a1.array(); 
 int[] arr2 = a2.array(); 
 for (int i=0; i value += arr1[i] + arr2[i]; 
 } 
 } 
 } 
 return value; 
 } 
 public void run() { 
 //... 
 } 
 }

对于4,如果线程进入,则线程在该类中所有操作不能进行,包括静态变量和静态方法,实际上,对于含有静态方法和静态变量的代码块的同步,我们通常用4来加锁。 

以上4种之间的关系: 

锁是和对象相关联的,每个对象有一把锁,为了执行synchronized语句,线程必须能够获得synchronized语句中表达式指定的对象的锁,一个对象只有一把锁,被一个线程获得之后它就不再拥有这把锁,线程在执行完synchronized语句后,将获得锁交还给对象。 

在方法前面加上synchronized修饰符即可以将一个方法声明为同步化方法。同步化方法在执行之前获得一个锁。如果这是一个类方法,那么获得的锁是和声明方法的类相关的Class类对象的锁。如果这是一个实例方法,那么此锁是this对象的锁。synchronzied块后面跟类的具体详细例子

、public class DB2_JDBCFactory {private static DB2_JDBCFactory instance = null; public static final ThreadLocal threadLocal = new ThreadLocal(); private DB2_JDBCFactory() { 
 } public static DB2_JDBCFactory getInstance() { 
 if(instance == null) { 
 synchronized(DB2_JDBCFactory.class) { //synchronized后面跟一个类 
 instance = new DB2_JDBCFactory(); 
 } 
 } 
 return instance; 
 } public Connection getConnection_JNDI_localhost(){ 
 Connection c = (Connection) threadLocal.get(); 
 try { 
 if (c == null || c.isClosed()) { 
 InitialContext ctx = new InitialContext(); 
 DataSource ds = (DataSource)ctx.lookup("java:comp/env/jdbc/localhost"); 
 c = ds.getConnection(); 
 threadLocal.set(c); 
 } 
 } catch (Exception ex) { 
 System.err.println("getConnection_JNDI Initial failed. " + ex); 
 return null; 
 } 
 return c;

}}外面的对象访问这个类的 需要通过调用它的getInstance()