在并发访问的问题上,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 1A synchronized loop 2A synchronized loop 3A synchronized loop 4B synchronized loop 0B synchronized loop 1B synchronized loop 2B synchronized loop 3B 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