synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。synchronized的作用主要有三个:

  1. 确保线程互斥的访问同步代码
  2. 保证共享变量的修改能够及时可见,即可见性。synchronized限制线程解锁前,必须把共享变量的最新值刷新到主内存中。线程加锁时,将清空工作内存中的共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意:加锁和解锁需要是同一把锁)
  3. 有效解决重排序问题。重排序通常是编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段。

synchronized从语法上讲有3种使用方式

  1. 修饰普通方法
  2. 修饰静态方法
  3. 修饰代码块

先通过javap反编译一段的代码来看看synchronized是如何实现对代码块进行同步的:

public class TestTest {

    public  void test(){
        synchronized (this){
            Object o= new Object();
        }
    }
}

 

反编译结果:

synchronized在Java中使用场景 synchronized的作用 java_多线程

 

和不加synchronized关键字对比,编译结果主要多出了monitorenter和monitorexit指令,通过jvm规范手册找到说明

monitorenter指令:https://docs.oracle.com/javase/specs/jvms/se14/html/jvms-6.html#jvms-6.5.monitorenter

Description

Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:

  1. If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
  2. If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
  3. If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.

简单翻译下

每个对象都有一个关联的监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

  1. 如果关联monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
  2. 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
  3. 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

monitorexit指令:https://docs.oracle.com/javase/specs/jvms/se14/html/jvms-6.html#jvms-6.5.monitorexit

Description

  1. The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
  2. The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

简单翻译下

  1. 只有拥有monitor的线程才可以执行monitorexit指令
  2. 执行monitorexit指令,monitor进入数-1,当进入数减为0时,线程退出monitor,该线程也不在是monitor的持有者,其他被阻塞的线程将被允许去获取monitor

 

当synchronized关键字修饰到方法上时

public class TestTest {

    public synchronized void test(){
            Object o= new Object();
    }
}

 

反编译结果

synchronized在Java中使用场景 synchronized的作用 java_html_02

同步方法上并没有和同步块一样的使用monitorenter和monitorexit指令(理论上其实也可以通过这两条指令来实现),不过在方法访问标识符(flags)中加入ACC_SYNCHRONIZED 标志

jvm规范手册的说明:https://docs.oracle.com/javase/specs/jvms/se14/html/jvms-2.html#jvms-2.11.10

Method-level synchronization is performed implicitly, as part of method invocation and return (§2.11.8). A synchronized method is distinguished in the run-time constant pool's method_info structure (§4.6) by the ACC_SYNCHRONIZED flag, which is checked by the method invocation instructions. When invoking a method for which ACC_SYNCHRONIZED is set, the executing thread enters a monitor, invokes the method itself, and exits the monitor whether the method invocation completes normally or abruptly. During the time the executing thread owns the monitor, no other thread may enter it. If an exception is thrown during invocation of the synchronized method and the synchronized method does not handle the exception, the monitor for the method is automatically exited before the exception is rethrown out of the synchronized method.

简单说明
       同步方法时隐式执行的,同步方法在运行时会在常量池的method_info结构中放入ACC_SYNCHRONIZED标志,当线程访问方法时,会去检查是否存在ACC_SYNCHRONIZED标志,若存在,则需要获取关联的监视器monitor锁(和monitorenter指令一样),然后执行方法。无论方法调用是正常完成还是发生异常都会释放关联的监视器monitor锁。

 

通过这里可以知道synchronized关键字实际底层是通过一个叫监视器monitor锁的来实现的。每一个对象都有一个相关联的monitor,线程通过修改monitor的进入数从而实现拥有和释放。

 

记录一个常刷到的面试题:为什么wait()和notify()属于Object类

一般会有两种问法趋向,为什么是在Object类中而不是其他的类,第二个wait()和notify()是和线程相关为什么没有在线程类里面

  • 原因一:Java中,任何对象都可以作为锁,既然wait是放弃对象锁,当然就要把wait定义在这个对象所属的类中。更通用一些,由于所有类都继承于Object,我们完全可以把wait方法定义在Object类中,这样,当我们定义一个新类,并需要以它的一个对象作为锁时,不需要我们再重新定义wait方法的实现,而是直接调用父类的wait(也就是Object的wait),此处,用到了Java的继承。
  • 原因二:有的人会说,既然是线程放弃对象锁,那也可以把wait定义在Thread类里面啊,新定义的线程继承于Thread类,也不需要重新定义wait方法的实现。然而,这样做有一个非常大的问题,一个线程完全可以持有很多锁,你一个线程放弃锁的时候,到底要放弃哪个锁?当然了,这种设计并不是不能实现,只是管理起来更加复杂。