• Tags
  •         
  • www.breakyizhan.com
  •    

    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():

    Java的并发编程:从synchronized保证线程安全的原理

    简单说一下上面那个图,这个 monitorenter和monitorexit代表了锁的获取和释放,而方法int xx()并不是按顺序执行的,如果遇到第9步ifle也会跳到第19步,执行完之后再执行monitorexit释放锁,所以从java的字节码指令可以看到也是有很多monitorexit这样的命令,也是不奇怪的。

     
    转载请保留页面地址:https://www.breakyizhan.com/java/6628.html