synchronized的用法汇总

本文详细介绍了Java中synchronized关键字的使用方法,包括synchronized方法和synchronized块,以及如何通过synchronized块来同步类变量、静态方法和非静态方法。文章还解释了使用synchronized块时的注意事项,特别是关于String类型变量同步的问题,以及如何避免同步破坏方法之间的同步性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

						synchronized的用法 
 
 
synchronized 关键字,代表这个方法加锁,相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程B(或者C、 D等)正在用这个方法,有的话要等正在使用synchronized方法的线程B(或者C 、D)运行完这个方法后再运行此线程A,没有的话,直接运行。它包括两种用法:synchronized 方法和 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 块来同步一个对象变量,也可以用来同步类中的静态方法和非静态方法。
(1)同步一个对象变量
public class MyData()
{
private static final Object radiusLock = new Object();
    public static void setRadius(float radius) {
        synchronized (MyData.radiusLock) {
            MyData.radius = radius;
        }
    }
}
这里使用synchronized 块确保MyData.radius不会同时被两个不同的方法访问。

我们可以通过synchronized块来同步特定的静态或非静态方法。要想实现这种需求必须为这些特性的方法定义一个类变量,然后将这些方法的代码用synchronized块括起来,并将这个类变量作为参数传入synchronized块。下面的代码演示了如何同步特定的类方法:

 package mythread;

 public class SyncThread extends Thread
  004  {
  005      private static String sync = "";
  006      private String methodType = "";
  007  
  008      private static void method(String s)
  009      {
  010          synchronized (sync)
  011          {
  012              sync = s;
  013              System.out.println(s);
  014              while (true);
  015          }
  016      }
  017      public void method1()
  018      {
  019          method("method1");
  020      }
  021      public static void staticMethod1()
  022      {
  023          method("staticMethod1");
  024      }
  025      public void run()
  026      {
  027          if (methodType.equals("static"))
  028              staticMethod1();
  029          else if (methodType.equals("nonstatic"))
  030              method1();
  031      }
  032      public SyncThread(String methodType)
  033      {
  034          this.methodType = methodType;
  035      }
  036      public static void main(String[] args) throws Exception
  037      {
  038          SyncThread sample1 = new SyncThread("nonstatic");
  039          SyncThread sample2 = new SyncThread("static");
  040          sample1.start();
  041          sample2.start();
  042      }
  043  } 


    运行结果如下:

method1
staticMethod1

 

    看到上面的运行结果很多读者可能感到惊奇。在上面的代码中method1和staticMethod1方法使用了静态字符串变量sync进行同步。这两个方法只能有一个同时执行,而这两个方法都会执行014行的无限循环语句。因此,输出结果只能是method1和staticMethod1其中之一。但这个程序将这两个字符串都输出了。
 
    出现这种结果的愿意很简单,我们看一下012行就知道了。原来在这一行将sync的值改变了。在这里要说一下Java中的String类型。String类型和Java中其他的复杂类型不同。在使用String型变量时,只要给这个变量赋一次值,Java就会创建个新的String类型的实例。如下面的代码所示:

 
String s = "hello";
System.out.println(s.hashCode());
s = "world";
System.out.println(s.hashCode());


    在上面的代码中。第一个s和再次赋值后的s的hashCode的值是不一样的。由于创建String类的实例并不需要使用new,因此,在同步String类型的变量时要注意不要给这个变量赋值,否则会使变量无法同步。
 
    由于在013行已经为sync创建了一个新的实例,假设method1先执行,当method1方法执行了013行的代码后,sync的值就已经不是最初那个值了,而method1方法锁定的仍然是sync变量最初的那个值。而在这时,staticMethod1正好执行到synchronized(sync),在staticMethod1方法中要锁定的这个sync和method1方法锁定的sync已经不是一个了,因此,这两个方法的同步性已经被破坏了。
 
    解决以上问题的方法当然是将013行去掉。在本例中加上这行,只是为了说明使用类变量来同步方法时如果在synchronized块中将同步变量的值改变,就会破坏方法之间的同步。为了彻底避免这种情况发生,在定义同步变量时可以使用final关键字。如将上面的程序中的005行可改成如下形式:


private final static String sync = "";


    使用final关键字后,sync只能在定义时为其赋值,并且以后不能再修改。如果在程序的其他地方给sync赋了值,程序就无法编译通过。在Eclipse等开发工具中,会直接在错误的地方给出提示。
 
    我们可以从两个角度来理解synchronized块。如果从类方法的角度来理解,可以通过类变量来同步相应的方法。如果从类变量的角度来理解,可以使用synchronized块来保证某个类变量同时只能被一个方法访问。不管从哪个角度来理解,它们的实质都是一样的,就是利用类变量来获得同步锁,通过同步锁的互斥性来实现同步。
 
    注意:在使用synchronized块时应注意,synchronized块只能使用对象作为它的参数。如果是简单类型的变量(如int、char、boolean等),不能使用synchronized来同步。


(2)通过synchronized块同步非静态方法
9 public class SyncBlock 
10  { 
11       public void method1() 
12       { 
13           synchronized(this)  // 相当于对method1方法使用synchronized关键字 
14           { 
15               … … 
16           } 
17       } 
18       public void method2() 
19       { 
20           synchronized(this)  // 相当于对method2方法使用synchronized关键字 
21           { 
22               … … 
23           } 
24       } 
25       public synchronized void method3()   
26       { 
27           … … 
28       } 
29   }
    在上面的代码中的method1和method2方法中使用了synchronized块。而第017行的method3方法仍然使用 synchronized关键字来定义方法。在使用同一个SyncBlock类实例时,这三个方法只要有一个正在执行,其他两个方法就会因未获得同步锁而被阻塞。在使用synchronized块时要想达到和synchronized关键字同样的效果,必须将所有的代码都写在synchronized块 中,否则,将无法使当前方法中的所有代码和其他的方法同步。
    除了使用this做为synchronized块的参数外,还可以使用SyncBlock.this作为synchronized块的参数来达到同样的效果。
    在内类(InnerClass)的方法中使用synchronized块来时,this只表示内类,和外类(OuterClass)没有关系。但内类的非静态方法可以和外类的非静态方法同步。如在内类InnerClass中加一个method4方法,并使method4方法和SyncBlock的三 个方法同步,代码如下:
使内类的非静态方法和外类的非静态方法同步
30 public class SyncBlock 
31 { 
32     … … 
33     class InnerClass 
34     { 
35         public void method4() 
36         { 
37             synchronized(SyncBlock.this) 
38             { 
39                 … …  
40             } 
41         } 
42     } 
43     … … 
44 }
    在上面SyncBlock类的新版本中,InnerClass类的method4方法和SyncBlock类的其他三个方法同步,因此,method1、method2、method3和method4四个方法在同一时间只能有一个方法执行。
    Synchronized块不管是正常执行完,还是因为程序出错而异常退出synchronized块,当前的synchronized块所持有的同步锁都会自动释放。因此,在使用synchronized块时不必担心同步锁的释放问题。
(3)、静态类方法的同步
    由于在调用静态方法时,对象实例不一定被创建。因此,就不能使用this来同步静态方法,而必须使用Class对象来同步静态方法。代码如下:
通过synchronized块同步静态方法
45 public class StaticSyncBlock 
46   { 
47       public static void method1() 
48       { 
49           synchronized(StaticSyncBlock.class)   
50           { 
51               … … 
52           } 
53       } 
54       public static synchronized void method2()   
55       { 
56           … … 
57       } 
58   }
    在同步静态方法时可以使用类的静态字段class来得到Class对象。在上例中method1和method2方法同时只能有一个方法执行。除了使用class字段得到Class对象外,还可以使用实例的getClass方法来得到Class对象。上例中的代码可以修改如下:
使用getClass方法得到Class对象
59 public class StaticSyncBlock 
60 { 
61     public static StaticSyncBlock instance;  
62     public StaticSyncBlock() 
63     { 
64         instance = this; 
65     } 
66     public static void method1() 
67     { 
68        synchronized(instance.getClass()) 
69        { 
70              
71        } 
72     } 
73       
74 } 
    在上面代码中通过一个public的静态instance得到一个StaticSyncBlock类的实例,并通过这个实例的getClass方法 得到了Class对象(一个类的所有实例通过getClass方法得到的都是同一个Class对象,因此,调用任何一个实例的getClass方法都可以)。我们还可以通过Class对象使不同类的静态方法同步,如Test类的静态方法method和StaticSyncBlock类的两个静态方法同 步,代码如下:
Test类的method方法和StaticSyncBlock类的method1、method2方法同步
75 public class Test 
76 { 
77     public static void method() 
78     { 
79         synchronized(StaticSyncBlock.class) 
80         { 
81               
82         } 
83     } 
84 }
注意:在使用synchronized块同步类方法时,非静态方法可以使用this来同步,而静态方法必须使用Class对象来同步。它们互不影 响。当然,也可以在非静态方法中使用Class对象来同步静态方法。但在静态方法中不能使用this来同步非静态方法。这一点在使用 synchronized块同步类方法时应注意。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值