`

java synchronized关键字

 
阅读更多

java synchronized关键字的总结:

1.Synchronized的关键字的作用域有两种:

1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其他线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是互不干扰的。也就是说,其他线程照样可以访问相同类的另一个实例中的synchronized方法;

2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对该类的所有的实例起作用。

 

2、除了方法前使用synchronized关键字,synchronized关键字还可以用于方法中的某个区块,中,表示只对这个区块的资源实行互斥访问用法是synchronized(this){ },其作用对象是当前对象。

3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){}在继承中并不是自动是synchronized(){},而是编程f(){}。继承需要显式的指定某个方法为synchronized方法。

 

java语言的关键字,当他用来修饰一个方法或者一个代码块的时候,能够保证在同一个时刻最多只有一个线程得到执行。

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

二、然而,当一个线程访问Object的synchronized(this)的同步代码块时,另一个线程可以执行该类的非synchronized(this)同步代码块。

三、尤其关键的是,当一个线程访问类中的某一个synchronized(this)同步代码块时,其他的synchronized代码块将是被阻塞的。

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

五、以上的规则对其他的对象锁同样适用。

 

第二篇

synchronized有两种用法:synchronized方法和synchronized块。

1、synchronized方法:通过在方法声明中加入synchronized关键字来声明synchronized方法。如:public synchronized void accessVal();

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

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

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

通过synchronized关键字来声明synchronized块。语法如下:synchronized(object){}

synchronized块是这样一个代码块,其中的代码必须获得object对象的锁方能执行,具体的机制如前面所述。可以针对任意的上锁的对象,灵活性较高。

对synchronized(this)的理解

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

二、然而,当一个线程访问类中的synchronized(this)同步代码块时,另一个线程依然可以访问该实例中的非synchronized(this)中的内容。

三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中的多有的synchronized(this)的同步代码块都会被阻塞。

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

 

 

 

第三篇

一个比喻:一个object就像一个大的房间,大门永远打开。房子里有很多房间(也就是方法)。

这些房间有上锁的(synchronized方法),和不上锁之分(普通方法)。门口放着一把钥匙(key),这个钥匙可以打开所有的上锁的房间。

在此我把所有想调用该对象方法的线程比喻成想进入这个房子某个房间的人。这就是多线程中的所有的角色。假设该对象上至少有一个synchronized方法,否则该key将没有意义。当然也就没有这个主题了。

 

         一个人想要进入某间上了锁的房间,他来到房子门口,看见钥匙在那儿(暂时还没有人要使用上锁的房间),于是他拿走了钥匙,并按照计划使用那些房间。注意一点,他每次使用完一次上锁的房间后马上就将钥匙还回去。即使他要连续使用两件上锁的房间,中间他也要将钥匙还回去,再取回来。因此,普通情况下钥匙的使用原则是:“随用随取,用完即还”。

这是其他人可以不受限制的使用那些没有上锁的房间,一个人可以,多个人也可以。此时,如果有人要进入上锁的房间,他就要跑到大门前看看。有钥匙就拿了就走,没有的话,就只能等了。没有的话就需要等。要是有很多人在等这把钥匙,等钥匙回来后,谁会优先得到钥匙 ,no guaranteed 。向前面的例子里那连续使用两个上锁房间的家伙在中间还钥匙的时候如果还有其他人在等钥匙,那么就没有任何保证这家伙能再次拿到。(java规范中很多地方都说明不保证,像Thread.sleep()休息多久后会返回运行,相同优先权哪个会被优先执行。当要访问的对象被锁,当要访问对象的锁被释放后处于等待池中多个线程哪个会优先得到,等等。最终的决定权在jvm,之所以不保证,是因为jvm在作出上诉决定的时候,绝不是根据简简单单根据一个条件来作出判断。由于判断的条件太多,太详细会影响java的推广,或者是因为知识产权保护的原因。但内部一定有一个详细的算法)。

在看看同步代码块。和同步方法有小小的不同。

1、从尺寸上将,同步代码比同步方法小。你可以将同步代码块看成是没上锁房间的一块用带锁的屏风隔开的空间。

2、同步代码还可以人为的指定获得某个其他对象的key。就像是指定用哪一把钥匙才能打开这个屏风的锁,你可以使用本房间的钥匙;你也可以指定用另一个房间的钥匙才能打开。这样的话,你要跑到另一栋房子那儿把钥匙拿来,并用那个房子的钥匙来打开这个房间的带锁的屏风。

      记住你获得的另一栋房子的钥匙,并不影响其他人进入那栋房子没有锁的房间。

     为什么要使用同步代码块呢?可能的原因:首先对程序来讲同步的部分很影响运行效率,而一个方法通常是先创建一些局部变量,在对这些变量做一些操作,如运算。操作等;而同步所覆盖的代码越多,对效率的影响就越严重。因此我们通常尽量缩小影响范围。

如何做:同步代码块。我们将一个方法中该同步的地方同步,比如运算。

另外,同步代码块可以指定钥匙这一特点有个额外的好处,是可以在一定时期内霸占某个对象的key。还记得前面所过的普通情况下钥匙的使用原则吗?现在不是普通情况了。所取得的锁不是永远不还,而是退出同步代码块时才还。

 

还用前面那个想连续用两个上锁房间的家伙打比喻。如何能在用完一间后,继续使用另一件呢。用同步代码块吧。先创建另外一个线程,做一个同步代码块,把那个代码块的锁指向这个房子的钥匙。然后启动那个线程。只要你能在进入那个代码块时抓住这房子的钥匙,你就可以一直保留到退出那个代码块。也就是说,你可以对本房间内所有上锁的房间遍历,甚至可以再Sleep(60*60),而外面还有1000个线程在等待这把钥匙。

   对于sleep()和钥匙的关联性讲下。一个线程拿到key后,且没有完成同步的内容时,如果被强制sleep时,那么key还一直在那儿。直到他再次运行,完成所有的同步的内容,才会归还key。记住,那家伙只是干活干累了,去休息一下,他并没干完他要干的事情。为了避免别人进入那个房间把里面搞得一团糟,即使睡觉的时候也要将那把钥匙戴在身上。

 

最后也许会有人会问,为什么要一把钥匙通开,而不是一把钥匙一个门呢?这可能是因为复杂性问题。一把钥匙一扇门当然会更加安全,但会迁出更多的问题。钥匙的保管,产生,获得,归还等问题。其复杂性可能会随着同步方法呈几何级数增加,严重影响效率。

 

package org.bupt.threadTest;

public class TxtThread implements Runnable
{
	
	int num =10;
	String str = new String();
	
	@Override
	public void run()
	{
		
		synchronized (str)
		{
			while(num >0)
			{
				try
				{
					Thread.sleep(10);
				} catch (InterruptedException e)
				{
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
				System.out.println(Thread.currentThread().getName() + "  this is " + num --);
			}
			
		}
		
	}

}

 

package org.bupt.threadTest;

public class MainTest
{
	
	public static void main(String[] args)
	{
		TxtThread tt = new TxtThread();  //
		
		new Thread(tt).start();
		new Thread(tt).start();
		new Thread(tt).start();
		new Thread(tt).start();
	}

}

 

运行结果:

 

Thread-0  this is 10
Thread-0  this is 9
Thread-0  this is 8
Thread-0  this is 7
Thread-0  this is 6
Thread-0  this is 5
Thread-0  this is 4
Thread-0  this is 3
Thread-0  this is 2
Thread-0  this is 1

 根据运行结果会得出结果为线程只有第一个会得到执行,其他的线程都会得到该线程的钥匙,但是都由于num为0而不会输出数字。其中在执行过程中,Thread.sleep(10)的过程中,并不会失去对象的锁。

 

 

java中对多线程的支持和同步机制深受大家喜爱,似乎使用synchronized关键字就可以轻松的解决多线程共享数据同步问题。到底如何还需要深入了解才可以定论。

 

总的来说,synchronized可作用于instance变量、object reference(对象)  、static函数  和class literals(类名称字面常量)身上。

 

在进一步阐释之前,我们明确几点:

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

B、每一个对象只有一个锁与之关联。

C、实现同步需要付出很大的系统开销,甚至可能造成死锁,所以尽量避免无谓的同步代码控制。

 

下面来讨论synchronized用到不同的地方会产生的影响:

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

1、把synchronized当做函数修饰符时,实例代码如下:

public synchronized void methodAAA()

{

/*

*/

}

 

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

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

public void methodAAA()

{

synchronized(this)  //(1)

{

 

}

}

 

一处的this指的是什么呢?他指的是调用这个方法的对象。可见同步方法的实质是将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对象的创建则需要7条。

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

Class Foo

{

  public synchronized static void methiodAAA()  //同步的static函数

{

//

}

 

public  void methodBBB()

{

synchronized(Foo.class)  //类名称字面常量

}
}

 

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

 

记得在《effective java》一书中看过将Fool.clss和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同步,并且,只返回这个对象的clone()--这样,调用端得到的局势对象副本的引用了。

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics