Java关键字synchronized详解

    synchronized 关键字,表明这个方法加锁,至关于无论哪个线程A每次运行到这个方法时,都要检查有没有其它正在用这个方法的线程B(或者C D等),有的话要等正在使用这个方法的线程B(或者C D)运行完这个方法后再运行此线程A,没有的话,直接运行 它包括两种用法:synchronized 方法和 synchronized 块。java

1. synchronized 方法:

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

  public synchronized void accessVal(int newVal);数组

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

  在 Java 中,不光是类实例,每个类也对应一把锁,这样咱们也可将类的静态成员函数声明为 synchronized ,以控制其对类的静态成员变量的访问。多线程

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

 

2. synchronized 块:

  经过 synchronized关键字来声明synchronized 块。语法以下:app

  synchronized(syncObject) {函数

  //容许访问控制的代码this

  }线程

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

 

 

 

对synchronized(this)的一些理解

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

  2、当一个线程访问object的一个synchronized(this)同步代码块时,其余线程对object中全部其它synchronized(this)同步代码块的访问将被阻塞。

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

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

  5、以上规则对其它对象锁一样适用

 

 

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用到不一样地方对代码产生的影响:

 

假设P一、P2是同一个类的不一样对象,这个类中定义了如下几种状况的同步块或同步方法,P一、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()――这样,调用端获得的就是对象副本的引用了

 

 

还有,比较经常使用的就有:Collections.synchronizedMap(new HashMap()),固然这个MAP就是生命在类中的全局变量,就是一个 线程安全的HashMap,web的application是全web容器公用的,因此要使用线程安全来保证数据的正确。

相关文章
相关标签/搜索