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

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

原文地址:https://www.cnblogs.com/xudilei/p/6836917.html