zoukankan      html  css  js  c++  java
  • synchronized关键字

    实现原理

    对于synchronized的基本了解就是作为锁来实现代码的同步,用法网上都有解释,下面也会写,但是这个底层是如何实现的,一直很想了解。
    synchronized是多线程锁常用的方法,可以对方法或者代码块加锁,但在底层实现方式基本都是一样的,主要使用监视器锁monitor,这里我对一段加锁代码进行了反编译。

    public class Lock1 {
    	public synchronized void method1(){
        	System.out.println("helloworld");
    	}
    	public void method2(){
        	synchronized (this){
            	System.out.println("test");
        	}
    	}
    }
    

    反编译之后

    首先来看method2,这里是对代码块加锁,反编译的结果很明显显示其中加入了monitor,主要使用方式是monitorenter和monitorexit来加锁和解锁。
    加锁阶段

    • 尝试获取monitor锁,如果当前锁的进入数为0,那么获取锁,并加进入数加1
    • 本线程另外的方法尝试获取该锁,同样获取锁,进入数加1
    • 非本线程方法尝试获取该锁,进入阻塞状态,等待monitor进入数变为0

    解锁阶段

    • monitor的进入数减1,如果减1后进入数为0,那线程退出monitor

    这机制保证了加锁代码的所有权和同步性,同时wait和notify也是在这基础上实现的。那么再看一下synchronized方法,这里和原方法几乎一致,因为方法同步区域是固定的,不需要手动去分割指令,直接通过关键字作为标识通知虚拟机在执行过程中去获取锁。

    用法

    synchronized主要是作为关键字在代码进行加锁,保证线程对同步代码访问的互斥,解决代码重排序的问题,主要有三种方式

    • 正常方法的同步
    • 代码块的同步
    • 静态方法的同步

    正常方法的同步

    synchronized最常使用的方式,对于一般方法的同步加锁。

    public synchronized void method1(){
        	System.out.println(Thread.currentThread().getName()+"->helloworld");
    	}
    

    对于这个方法的调用,每个线程之间是同步的,会出现结果如

    Thread-0->helloworld
    Thread-1->helloworld
    Thread-2->helloworld
    

    之前我遇到过一个误区,如果一个类里存在多个加synchronized的方法,方法之间是同步的,因为这些方法获取的是对象锁,只允许被单一线程执行,不能异步调用。

    代码块的同步

    代码块的同步主要用于局部加锁,因为方法加锁的资源消耗太大,只需要对需要同步的内容加锁就可以实现目的,不需要全部加锁。

    public volatile int i = 0;
    public void add(){
    	synchronized(this){
    		for(int j = 0;j<100;j++){
    			i++;
    		}
    	}
    }
    

    我们知道修改volatile变量的操作是非原子型的,为了保证整个的原子性,就可以像这样对自增操作进行加锁,而非全部加锁。这里需要注意的是这个this,这里的意思是加锁的对象监视器是该类的对象,当然可以不是这个,我们可以定义一个公共类作为对象监视器,这个时候会存在这样的情况

    public volatile int i = 0;
    public String lock1 = "1";
    public String lock2 = "2"
    public void add(){
    	synchronized(lock1){
    		for(int j = 0;j<100;j++){
    			i++;
    		}
    	}
    	synchronized(lock1){
    		System.out.println(i);
    	}
    	synchronized(lock2){
    		System.out.println(i+1);
    	}
    }
    

    这里是有两个锁,前两个synchronized是同步,即其中一个被线程调用的时候,另一个也只能被该线程调用,但是第三个synchronized不是,因为它的对象没有被锁,是可以被任何线程执行的。

    静态方法的同步

    其实静态方法和一般方法的同步原理是一致的,但是在对象监视器的获取上存在差异,一般的获取的是我们new的变量类,但是静态方法获取的则是类本身。

    public class StaticLock {
    	public static synchronized void method1(){
        	for(int i = 0;i<3;i++){
            	System.out.println(Thread.currentThread().getName()+"->"+i);
        	}
    	}
    
    	public static synchronized void method2(){
        	for(int i = 0;i<3;i++){
            	System.out.println(Thread.currentThread().getName()+"->"+i);
        	}
    	}
    
    	public static void main(String[] args) {
        	final StaticLock methodA = new StaticLock();
        	final StaticLock methodB = new StaticLock();
        	new Thread(){
            	@Override
            	public void run() {
                	methodA.method1();
            	}
        	}.start();
        	new Thread(){
            	@Override
            	public void run() {
                	methodB.method1();
            	}
        	}.start();
    	}
    }
    

    其运行结果是

    Thread-0->0
    Thread-0->1
    Thread-0->2
    Thread-1->0
    Thread-1->1
    Thread-1->2
    

    假如是一般方法的加锁,不可能存在线程一和二的同步执行,因为定义两个对象,锁不同,但是静态方法的锁获取的是类锁,定义再多对象,锁还是一个。

  • 相关阅读:
    【BUG】java.sql.SQLException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more than one time zone
    IntelliJ IDEA控制台输出中文乱码问题
    CMD命令
    MongoDB学习笔记
    MyBatis生成序列ID
    MongoDB配置问题
    正确处理下载文件时HTTP头的编码问题(Content-Disposition)
    SpringJPA主键生成采用自定义ID,自定义ID采用年月日时间格式
    Java根据经纬度算出附近正方形的四个角的经纬度
    gradle
  • 原文地址:https://www.cnblogs.com/xudilei/p/6836917.html
Copyright © 2011-2022 走看看