在Java的并发编程:线程的安全性问题的分析这篇文章中说到了synchronized可以保证线程的安全性,那么,这篇文章主要是说synchronized保证线程安全的原理,为什么加上synchronized就能保证线程的安全呢?我们可以从两个角度出发去看这个问题,一个是从理论的层面,一个是从JVM的层面。
从理论的层面看synchronized保证线程安全的原理
synchronized的原理有两个:
- 内置锁
- 互斥锁
Java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。而Java的内置锁又是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,知道线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。
synchronized保证线程的安全访问,那么,这个synchronized的关键字可以修饰什么呢?synchronized主要是用来修饰下面三个内容:
- 修饰普通方法
- 修饰静态方法
- 修饰代码块
synchronized修饰这个三个方法的Java示例:
package com.breakyizhan.thread.t3; public class Sequence { private int value; /** * synchronized 放在普通方法上,内置锁就是当前类的实例 * @return */ public synchronized int getNext() { return value ++; } /** * 修饰静态方法,内置锁是当前的Class字节码对象 * Sequence.class * @return */ public static synchronized int getPrevious() { // return value --; return 0; } public int xx () { // monitorenter synchronized (Sequence.class) { if(value > 0) { return value; } else { return -1; } } // monitorexit } public static void main(String[] args) { Sequence s = new Sequence(); // while(true) { // System.out.println(s.getNext()); // } new Thread(new Runnable() { @Override public void run() { while(true) { System.out.println(Thread.currentThread().getName() + " " + s.getNext()); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); new Thread(new Runnable() { @Override public void run() { while(true) { System.out.println(Thread.currentThread().getName() + " " + s.getNext()); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); new Thread(new Runnable() { @Override public void run() { while(true) { System.out.println(Thread.currentThread().getName() + " " + s.getNext()); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } }
从JVM的层面看synchronized保证线程安全的原理
我们已经知道,在Java的并发编程:线程的安全性问题的分析这篇文章中说到,用java -verbose可以查看JVM虚拟机的二进制代码,那么,我们就来看一下synchronized修饰同步代码块的时候运行的机制,(修饰普通方法和修饰静态方法暂时看不到锁的获取和释放),在synchronized修饰同步代码块的时候 monitorenter和monitorexit代表了锁的获取和释放,如下图,代表着方法int xx():
简单说一下上面那个图,这个 monitorenter和monitorexit代表了锁的获取和释放,而方法int xx()并不是按顺序执行的,如果遇到第9步ifle也会跳到第19步,执行完之后再执行monitorexit释放锁,所以从java的字节码指令可以看到也是有很多monitorexit这样的命令,也是不奇怪的。