多语言展示
当前在线:896今日阅读:167今日分享:16

JAVA线程安全:[2]synchronized关键字

前面我们谈了下JAVA内存模型的问题,这一部分我们来讨论下synchronized关键字在JAVA线程安全中的应用。
工具/原料

电脑一台(装JAVA)

方法/步骤
1

上面说了,java用synchronized关键字做为多线程并发环境的执行有序性的保证手段之一。当一段代码会修改共享变量,这一段代码成为互斥区或临界区,为了保证共享变量的正确性,synchronized标示了临界区。典型的用法如下:Java代码   synchronized(锁){         临界区代码    }   synchronized(锁){     临界区代码}   为了保证银行账户的安全,可以操作账户的方法如下:Java代码   public synchronized void add(int num) {         balance = balance + num;    }    public synchronized void withdraw(int num) {         balance = balance - num;    }  public synchronized void add(int num) {     balance = balance + num;}public synchronized void withdraw(int num) {     balance = balance - num;}  刚才不是说了synchronized的用法是这样的吗:Java代码   synchronized(锁){    临界区代码    }

2

那么对于public synchronized void add(int num)这种情况,意味着什么呢?其实这种情况,锁就是这个方法所在的对象。同理,如果方法是public  static synchro www.gzlij.com nized void add(int num),那么锁就是这个方法所在的class。        理论上,每个对象都可以做为锁,但一个对象做为锁时,应该被多个线程共享,这样才显得有意义,在并发环境下,一个没有共享的对象作为锁是没有意义的。假如有这样的代码:Java代码  public class ThreadTest{      public void test(){         Object lock=new Object();         synchronized (lock){            //do something         }      }   }        lock变量作为一个锁存在根本没有意义,因为它根本不是共享对象,每个线程进来都会执行Object lock=new Object();每个线程都有自己的lock,根本不存在锁竞争。

3

每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列,就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程,当一个被线程被唤醒(notify)后,才会进入到就绪队列,等待cpu的调度。当一开始线程a第一次执行account.add方法时,jvm会检查锁对象account的就绪队列是否已经有线程在等待,如果有则表明account的锁已经被占用了,由于是第一次运行,account的就绪队列为空,所以线程a获得了锁,执行account.add方法。如果恰好在这个时候,线程b要执行account.withdraw方法,因为线程a已经获得了锁还没有释放,所以线程b要进入account的就绪队列,等到得到锁后才可以执行。

4

一个线程执行临界区代码过程如下:1 获得同步锁2 清空工作内存3 从主存拷贝变量副本到工作内存4 对这些变量计算5 将变量从工作内存写回到主存6 释放锁可见,synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性。

推荐信息