`
folksy
  • 浏览: 160304 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

java 多线程锁机制Synchronized

阅读更多

http://www.sunxin.org/forum/thread/19757.html

java 多线程锁机制Synchronized

<script type="text/javascript"></script>

      [关键字: Synchronized ]

打个比方:一个object就像一个大房子,大门永远打开。房子里有很多房间(也就是方法)。这些房间有上锁的(synchronized方法),和不上锁之分(普通方法)。房门口放着一把钥匙(key),这把钥匙可以打开所有上锁的房间。另外我把所有想调用该对象方法的线程比喻成想进入这房子某个房间的人。所有的东西就这么多了,下面我们看看这些东西之间如何作用的。

在此我们先来明确一下我们的前提条件。该对象至少有一个synchronized方法,否则这个key还有啥意义。当然也就不会有我们的这个主题了。

一个人想进入某间上了锁的房间,他来到房子门口,看见钥匙在那儿(说明暂时还没有其他人要使用上锁的房间)。于是他走上去拿到了钥匙,并且按照自己的计划使用那些房间。注意一点,他每次使用完一次上锁的房间后会马上把钥匙还回去。即使他要连续使用两间上锁的房间,中间他也要把钥匙还回去,再取回来。

因此,普通情况下钥匙的使用原则是:“随用随借,用完即还。”

这时其他人可以不受限制的使用那些不上锁的房间,一个人用一间可以,两个人用一间也可以,没限制。但是如果当某个人想要进入上锁的房间,他就要跑到大门口去看看了。有钥匙当然拿了就走,没有的话,就只能等了。

要是很多人在等这把钥匙,等钥匙还回来以后,谁会优先得到钥匙?Not guaranteed。象前面例子里那个想连续使用两个上锁房间的家伙,他中间还钥匙的时候如果还有其他人在等钥匙,那么没有任何保证这家伙能再次拿到。(JAVA规范在很多地方都明确说明不保证,象Thread.sleep()休息后多久会返回运行,相同优先权的线程那个首先被执行,当要访问对象的锁被释放后处于等待池的多个线程哪个会优先得到,等等。我想最终的决定权是在JVM,之所以不保证,就是因为JVM在做出上述决定的时候,绝不是简简单单根据一个条件来做出判断,而是根据很多条。而由于判断条件太多,如果说出来可能会影响JAVA的推广,也可能是因为知识产权保护的原因吧。SUN给了个不保证就混过去了。无可厚非。但我相信这些不确定,并非完全不确定。因为计算机这东西本身就是按指令运行的。即使看起来很随机的现象,其实都是有规律可寻。学过计算机的都知道,计算机里随机数的学名是伪随机数,是人运用一定的方法写出来的,看上去随机罢了。另外,或许是因为要想弄的确定太费事,也没多大意义,所以不确定就不确定了吧。)

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

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

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

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

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

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

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

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

          最后,也许有人会问,为什么要一把钥匙通开,而不是一个钥匙一个门呢?我想这纯粹是因为复杂性问题。一个钥匙一个门当然更安全,但是会牵扯好多问题。钥匙的产生,保管,获得,归还等等。其复杂性有可能随同步方法的增加呈几何级数增加,严重影响效率。这也算是一个权衡的问题吧。为了增加一点点安全性,导致效率大大降低,是多么不可取啊。

 

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()――这样,调用端得到的就是对象副本的引用了。

Java代码

01.// 以下是我学习过程中自己写的注释和例子,希望对大家有帮助!不妥之处望指出
02.  
03./**
04.   * java同步实现的两种方法,都是使用synchronized关键字:
05.   * 1:实现时就是放该关键字到实例方法或类方法前面,如:public synchronized void method (){ }
06.   * 2:采用synchronized块,使用如下:synchronized (Expression) { Block } 其中Expression为对象的引用,
07.   *    在进入同步块Block之前,必须在Expression上取得锁。如果已有其他线程取得了这把锁,块便不能进入,
08.   *    必须等候那把锁被释放。----表示只对Expression引用的对象才锁定代码块Block,对其它对象不锁
09.   * 在方法前作为修饰词和用到statement块的差别在于:前者是在运行时实现锁功能,后者在编译时生成的class文件中体现锁机制。
10.   
11.   * synchronized修饰方法时,表示该方法是同步的,同一时刻只能有一个线程使用它。
12.   
13.   * 下面列出简单的synchronized方法:
14.   * synchronized void f()
15.   * synchronized void g() 
16.   * 每个对象都包含了一把锁(也叫作“监视器”),它自动成为对象的一部分(不必为此写任何特殊的代码)。
17.   * 调用任何synchronized方法时,对象就会被锁定,不可再调用那个对象的其他任何synchronized方法,
18.   * 除非第一个方法完成了自己的工作,并解除锁定。在上面的例子中,如果为一个对象调用f(),便不能再为
19.   * 同样的对象调用g(),除非f()完成并解除锁定。因此,一个特定对象的所有synchronized方法都共享着一把锁,
20.   * 而且这把锁能防止多个方法对通用内存同时进行写操作(比如同时有多个线程)。
21.   * 每个类也有自己的一把锁(作为类的Class对象的一部分),所以synchronized static方法可在一个类的
22.   * 范围内被相互间锁定起来,防止与static数据的接触。
23.   *
24.   *     synchronized方法控制对类成员变量的访问:每个类实例对应一把锁,每个被synchronized的方法都必须获得
25.   * 调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释
26.   * 放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声
27.   * 明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),
28.   * 从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为synchronized)。
29.*     也可以将类的静态成员函数声明为synchronized ,以控制其对类的静态成员变量的访问。
30.   *     synchronized方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率,典型地,若将线程类
31.   * 的方法run()声明为synchronized,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何synchronized
32.   * 方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为synchronized,
33.   * 并在主方法中调用来解决这一问题,但是 Java 为我们提供了更好的解决办法,那就是synchronized块。
34.   
35.   * synchronized是对同一个对象而言的,即一个对象在执行被synchronized锁定的方法或者代码块时是同步的(不会被打断)
36.   
37.    */
38.public class ThreadTest {
39.  
40.private void method(Object o) throws InterruptedException{
41.   Thread t=Thread.currentThread();
42.   synchronized(t){
43.    for(int i=0;i<10;i++){
44.     System.out.print(t.getName()+" ");
45.     Thread.sleep(100);
46.    }
47.   }
48.   /*
49.   * 把synchronized(t)换成synchronized(o),比较运行结果
50.   * synchronized(o)要求共用一把"锁",所以要求要用同一个对象
51.   * synchronized(o)起到同步效果,synchronized(t)则没同步效果
52.   * 还有:用synchronized修饰method(Object o),看效果
53.   */
54.}
55.  
56.public static void main(String args[]){
57.    
58.   final ThreadTest th=new ThreadTest();
59.   final Object o=new Object(); 
60.   Thread t0=new Thread( new Runnable(){
61.    public void run(){
62.     System.out.println("\nT0 Start");   
63.                try {
64.      th.method(o);
65.     } catch (InterruptedException e){
66.      e.printStackTrace();
67.     }   
68.                System.out.println("\nT0 End"); 
69.                }
70.    });
71.   Thread t1=new Thread( new Runnable(){
72.    public void run(){
73.     System.out.println("\nT1 Start");   
74.                try {
75.      th.method(o);
76.     } catch (InterruptedException e){
77.      e.printStackTrace();
78.     }   
79.                System.out.println("\nT1 End"); 
80.                }
81.    });   
82.    
83.   t0.start(); 
84.   t1.start();
85.    
86.}
87.  
88.}

本文由rongke于2009-07-07 18:38:42.0在http://www.faceye.com发布 

 

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(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;

synchronized的一个简单例子:

Java代码

01.public class TextThread 
02.{
03.  
04./**
05.   * @param args
06.   */
07.public static void main(String[] args) 
08.{
09.   // TODO 自动生成方法存根
10.         TxtThread tt = new TxtThread();
11.         new Thread(tt).start();
12.         new Thread(tt).start();
13.         new Thread(tt).start();
14.         new Thread(tt).start();
15.}
16.  
17.}
18.class TxtThread implements Runnable
19.{
20.int num = 100;
21.String str = new String();
22.public void run()
23.{
24.   while (true)
25.   {
26.    synchronized(str)
27.    {
28.    if (num>0)
29.    {
30.     try
31.     {
32.      Thread.sleep(10);
33.     }
34.     catch(Exception e)
35.     {
36.      e.getMessage();
37.     }
38.     System.out.println(Thread.currentThread().getName()+ "this is "+ num--);
39.    }
40.    }
41.   }
42.}
43.}

 

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

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

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

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

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

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

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

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

 

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

 

分享到:
评论

相关推荐

    java锁机制Synchronizedjava锁机制Synchronized

    Java 锁机制 Synchronized 是 Java 语言中的一种同步机制,用于解决多线程并发访问共享资源时可能出现的一些问题。 Java 锁机制 Synchronized 的概念 在 Java 中,每个对象都可以被看作是一个大房子,其中有多个...

    java的线程同步机制synchronized关键字的理解_.docx

    Java 线程同步机制中 synchronized 关键字的理解 Java 的线程同步机制是为了解决多个线程共享同一片存储空间所带来的访问冲突问题。其中,synchronized 关键字是 Java 语言中解决这种冲突的重要机制。 ...

    Java多线程同步机制研究分析.pdf

    Java多线程同步机制有两种类型:synchronized方法和synchronized块。synchronized方法是将访问共享资源的方法标记为synchronized,然后该标记的方法来控制对类成员变量的访问。synchronized块是将程序的某段代码使用...

    java 多线程synchronized互斥锁demo

    标题中的"java 多线程synchronized互斥锁demo"指的是一个示例,展示了如何在多线程环境下使用`synchronized`关键字创建互斥锁,确保同一时间只有一个线程可以访问特定的代码块或方法。 描述中的"一个多线程访问的同...

    Java多线程同步机制的应用分析.pdf

    "Java多线程同步机制的应用分析" Java多线程同步机制的应用分析是指在Java...* Java语言的多线程机制 * 管程机制和同步语法的详细介绍 * Java多线程同步机制在售票系统中的应用 * Java多线程同步机制的优点和缺点分析

    java多线程之并发锁

    Java 中的多线程编程需要充分考虑线程安全和锁机制的问题,否则可能会导致程序的执行不稳定和崩溃。Lock 机制是 Java 中的一种重要的线程同步机制,它可以用来实现线程安全和提高程序的执行效率。

    Java多线程机制(讲述java里面与多线程有关的函数)

    Java多线程机制是Java编程中至关重要的一部分,它允许程序同时执行多个任务,提升应用程序的效率和响应性。以下是对各个知识点的详细说明: 9.1 Java中的线程: Java程序中的线程是在操作系统级别的线程基础上进行...

    JAVA多线程的锁机制和无锁并行.docx

    JAVA 多线程的锁机制和无锁并行 JAVA 多线程编程中,锁机制是保证线程安全的重要手段之一。锁机制可以分为内部锁和外部锁两种,内部锁又称为监视器或内部锁,它是一种非公平的排它锁,能够保障原子性、可见性和...

    java多线程机制 详解

    Java的多线程机制是Java语言的一大特性,它允许程序同时执行多个任务,提升程序响应速度,优化资源利用率。在Java中,线程是程序执行的最小单位,一个进程可以包含多个线程,每个线程都有自己独立的生命周期,包括...

    Java多线程知识点总结

    Java多线程是Java编程语言中一个非常重要的概念,它允许开发者在一个程序中创建多个执行线程并行运行,以提高程序的执行效率和响应速度。在Java中,线程的生命周期包含五个基本状态,分别是新建状态(New)、就绪...

    Java多线程通信机制研究.pdf

    Java多线程通信机制的实现方法包括使用synchronized关键字来实现同步,使用wait()和notify()方法来实现线程间的通信。线程之间的通信是通过共享变量和信号量来实现的。共享变量是指多个线程之间共享的变量,而信号量...

    java多线程、锁的教程跟案例

    Java多线程与锁是Java并发编程中的核心概念,它们对于构建...总之,理解并熟练掌握Java多线程和锁机制对于编写高效、健壮的并发代码至关重要。通过实践和案例学习,可以更好地理解这些概念,并能在实际项目中灵活运用。

    JAVA多线程练习题答案。

    JAVA多线程练习题答案详解 在本文中,我们将对 JAVA 多线程练习题的答案进行详细的解释和分析。这些题目涵盖了 JAVA 多线程编程的基本概念和技术,包括线程的生命周期、线程同步、线程状态、线程优先级、线程安全等...

    java多线程经典案例

    Java多线程是Java编程中的重要概念,它允许程序同时执行多个任务,极大地提升了程序的效率和性能。在Java中,实现多线程有两种主要方式:通过实现Runnable接口或者继承Thread类。本案例将深入探讨Java多线程中的关键...

    java多线程的讲解和实战

    Java多线程是Java编程中的重要概念,尤其在如今的多核处理器环境下,理解并熟练掌握多线程技术对于提高程序性能和响应速度至关重要。本资料详细讲解了Java多线程的原理,并提供了丰富的实战代码,非常适合Java初学者...

    java多线程进度条

    本主题将深入探讨如何在Java多线程环境下实现进度条功能。 首先,理解Java多线程的基本概念至关重要。Java通过Thread类和Runnable接口来支持多线程。创建一个新线程通常有两种方式:继承Thread类并重写run()方法,...

    java多线程详解(比较详细的阐述了多线程机制)

    本文将深入探讨Java多线程机制,包括线程的创建、同步、通信以及常见设计模式。 首先,Java中创建线程主要有两种方式:通过实现Runnable接口和继承Thread类。实现Runnable接口更灵活,因为Java是单继承的,而通过...

    Java多线程同步.pdf

    Java多线程同步是指在Java语言中,如何使用synchronized关键字和其他同步机制来确保多线程程序的正确执行。在Java语言中,synchronized关键字用于对方法或者代码块进行同步,但是仅仅使用synchronized关键字还不能...

Global site tag (gtag.js) - Google Analytics