Java中synchronized同步的理解

在并发访问的问题上,Java引入了同步监视器来应对,主要是通过关键字synchronized实现。关于synchronized,它有两种形式,一种是同步代码块:synchronized(obj){},另一种是同步方法:public synchronized void method1(){},前者比较灵活,可以自己控制同步的范围,而后者同步的是整个方法。

同步代码块

synchronized(obj){}

在上述代码中,对象obj即是同步监视器,代码表示,如果要执行{}中的代码,必须先获得对obj的锁定。该对象可以是任何对象,但是一般情况下推荐使用那个要被并发访问的对象,因为同步监视器的作用就是防止对某资源进行并发访问。比如

 1 package com.hm.thread.test;
 2 
 3 public class Thread111 implements Runnable {
 4     public Object o = new Object();
 5     public void run() {
 6         synchronized (o) {
 7             for (int i = 0; i < 5; i++) {
 8                 System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
 9             }
10         }
11     }
12 
13     public static void main(String[] args) {
14         Thread111 t1 = new Thread111();
15         Thread ta = new Thread(t1, "A");
16         Thread tb = new Thread(t1, "B");
17         ta.start();
18         tb.start();
19     }
20 }

如果不加synchronized (o) {},则输出的A,B是交错的,因为两个线程A,B并发执行,所以打印的结果就是交替输出。而使用了synchronized (o) {}之后,两个线程都要去获取o的锁,那么就变成了同步执行了,A和B分别输出:

A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4

 同步方法

public synchronized void method1(){}

对于同步方法来说,没有显式的指定同步监视器,实际的同步监视器是this,即调用该方法的对象本身。

这里有一点需要注意:是否是同一个对象在多线程的访问同步方法。

这里借用一些例子说明,比如:

 1 package com.hm.thread.account;
 2 
 3 class Account {
 4     String name;
 5     float amount;
 6 
 7     public Account(String name, float amount) {
 8         this.name = name;
 9         this.amount = amount;
10     }
11 
12     public synchronized void deposit(float amt) {
13         float tmp = amount;
14         tmp += amt;
15         try {
16             Thread.sleep(1);// 模拟其它处理所需要的时间,比如刷新数据库等
17         } catch (InterruptedException e) { }
18         amount = tmp;
19     }
20 
21     public synchronized void withdraw(float amt) {
22         float tmp = amount;
23         tmp -= amt;
24         try {
25             Thread.sleep(114);// 模拟其它处理所需要的时间,比如刷新数据库等
26         } catch (InterruptedException e) { }
27         amount = tmp;
28     }
29 
30     public float getBalance() {
31         return amount;
32     }
33 }
34 
35 public class AccountTest {
36     private static int NUM_OF_THREAD = 200;
37     static Thread[] threads = new Thread[NUM_OF_THREAD];
38 
39     public static void main(String[] args) {
40         final Account acc = new Account("John", 1000.0f);
41         for (int i = 0; i < NUM_OF_THREAD; i++) {
42             threads[i] = new Thread(new Runnable() {
43                 public void run() {
44                     acc.deposit(100.0f);
45                     acc.withdraw(100.0f);
46                 }
47             });
48             threads[i].start();
49         }
50 
51         for (int i = 0; i < NUM_OF_THREAD; i++) {
52             try {
53                 threads[i].join(); // 等待所有线程运行结束
54             } catch (InterruptedException e) {
55             }
56         }
57         System.out.println("Finally, John's balance is:" + acc.getBalance());
58     }
59 
60 }

在上面的例子中,模拟账户中有1000元,存取款各100元,各200次,理论上余额应该还是1000,但是对于deposite()和 withdraw()方法,如果去掉synchronized修饰,则结果是不确定的。加上synchronized,则对于同一个account对象,在多个线程中并发访问deposite()和withdraw()方法,这两个方法都变成同步的了,即多个线程被同步了。

那么换个例子

 1 package com.hm.thread.test;
 2 
 3 public class ClassSync_Test {
 4     public static void main(String[] args) {
 5         Foo f1 = new Foo(1);
 6         Foo f2 = new Foo(3);
 7         f1.start();
 8         f2.start();
 9     }
10 }
11 
12 class Foo extends Thread {
13     private int val;
14     public Foo(int val){
15         this.val = val;
16     }
17     
18     public synchronized void print(int v){
19             int n = 100;
20             int i = 0;
21             while(i < n){
22                 System.out.println(v);
23                 i++;
24                 try {
25                     Thread.currentThread().sleep(1);
26                 } catch (InterruptedException e) {
27                     e.printStackTrace();
28                 }
29             }
30     }
31     public void run(){
32         print(val);
33     }
34 }

在这个例子中,print()也是同步方法,但是在main方法中new出了两个Foo对象,分别启动线程执行,可以看到1和3交替出现,说明这个方法同时被两个线程执行了,而并不是同步的,原因在哪?

在Java中,类本身只有一个实例,但是类产生的对象却可以多个。对于Foo,它存在两个实例对象,启动它们的线程,彼此并不干扰,所以它们各自执行自己的同步方法。换句话说,synchronized虽然锁住了这个方法,但是只是针对同一个对象来说的,不同的对象会获取了各自的锁,彼此没有影响。所以,如果要实现同步,则需要让不同的对象在访问这个方法的时仍然去获取同一把锁,而不是各自获取各自的。即让synchronized去同步一个类级别的对象。

可以这么修改print():

 1 public void print(int v){
 2         synchronized(Foo.class){
 3             int n = 100;
 4             int i = 0;
 5             while(i < n){
 6                 System.out.println(v);
 7                 i++;
 8                 try {
 9                     Thread.currentThread().sleep(1);
10                 } catch (InterruptedException e) {
11                     e.printStackTrace();
12                 }
13             }
14         }
15     }

当锁住Foo.class之后,该类对应的对象在访问print方法时都会去获取Foo.class身上的锁--同一把锁。所以实现了同步。或者可以在该类中创建一个全局的静态变量,然后替换掉Foo.class也是可以的,因为静态的变量和类一样,都只有一份(要注意锁住一个class,范围较大,性能不好)。

另外一方面,对于wait(),notify(),notifyAll()三个方法,它们属于Object类,并且必须由同步监视器调用。所以在同步代码块中,使用obj去调用,obj.wait()。而对于同步方法来说,由于它没有显示指定同步监视器,并且监视器就是this,所以可以直接调用:this.wait()或者wait()。

引用:

http://www.shangxueba.com/jingyan/90315.html

http://www.cnblogs.com/devinzhang/archive/2011/12/14/2287675.html#top

 

posted on 2016-01-20 14:51  涩谷直子  阅读(400)  评论(0编辑  收藏  举报