• 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
    

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

  • 相关阅读:
    luffy-短信接口频率限制,以及前端发送,和注册
    腾讯云短信开发
    luffy-登录注册页面
    vue之vue-cookies 获取、设置以及删除指定的cookies
    pycharm操作git
    luffy的前端主页
    vue路由跳转的方式
    django 中的Model继承
    跨域请求CORS详解
    vue:实现图书购物车
  • 原文地址:https://www.cnblogs.com/xudilei/p/6836917.html
Copyright © 2020-2023  润新知