美文网首页
我对synchronized的一点理解

我对synchronized的一点理解

作者: keeperforone | 来源:发表于2019-01-09 14:55 被阅读0次

为什么需要同步

摘抄jdk文档原话 :Threads communicate primarily by sharing access to fields and the objects reference fields refer to. This form of communication is extremely efficient, but makes two kinds of errors possible: thread interference and memory consistency errors. The tool needed to prevent these errors is synchronization.

大概意思是:线程主要通过共享对字段的访问和参考字段引用的对象进行通信。这种通信形式非常有效,但可能出现两种错误:线程干扰和内存一致性错误。防止这些错误所需的工具是同步。--摘抄jdk文档原话

https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html

synchronized是什么

摘抄jdk文档原话 :Synchronized methods enable a simple strategy for preventing thread interference and memory consistency errors: if an object is visible to more than one thread, all reads or writes to that object's variables  are done through synchronized methods

大概意思是:同步方法支持一种简单的策略来防止线程干扰和内存一致性错误:如果一个对象对多个线程可见,则对该对象变量的所有读取或写入都是通过synchronized方法完成的。

synchronized使用

synchronized的使用方式有两种:synchronized方法和synchronized语句

1:synchronized方法,实例方法和静态方法均可使用

public synchronized void sync(){

}

public synchronized static void sync1(){

}

2:synchronized语句

public final static Object MUTEX = new Object();

public void sync(){

    synchronized(MUTEX){

    }

}

synchronized深入解析

synchronized关键字提供了一种互斥机制,主要作用于上面介绍的synchronized方法和synchronized语句,我们可以将synchronized关键字的使用看作为一种同步操作。

同步操作是通过对象内部关联的monitor锁构建的,主要发挥了两个方面的作用:强制对对象状态进行独占访问,并建立与此可见性相关的happend-before关系。

我们可以这样理解

synchronized(MUTEX)这个同步操作是一个线程获取一个与MUTEX关联的monitor锁,当线程获取到对象的monitor锁之后就对这个对象独占了,其他线程就无法获取到这个对象的monitor锁,只有当对象的monitor锁被释放后才可以尝试获取,monitor锁的获取和释放都遵循happend-before原则。

当synchronized作用在方法上,它会自动获取该方法对象的monitor锁,并在方法返回时释放monitor锁。即使在返回的时发生了未捕获的异常,也会正常释放monitor锁,这个获取和释放的动作我们无从感知,所以synchronzied也称为隐式锁。

当使用synchronized语句时就必须指定一个monitor锁的对象,获取monitor锁、释放的操作跟作用在方法的方式是一致的,只是前者需要特别指定,后者自动获取方法对象的monitor锁。

需要注意的一点是monitor关联的对象不能为空

代码分析

创建5条线程同时去调用accessResource方法,由于accessResource使用了synchronized提供的同步操作,当有一条线程获取到方法对象的monitor锁之后,其他四条线程会进入堵塞状态,直到monitor锁被释放后。这个我们可以通Jconsole查看一下线程的状态(线程休眠10分钟可以更细致的查看)。

先运行程序,在终端输入jconsole指令即可。

我们也可以通过jstack指令查看线程的堆栈信息,输入jstack -pid

在线程的堆栈信息我们可以看到Thread0持有monitor锁并处于休眠状态中“locked<0x00000007956143c0>”,而且其他线程都在等待monitor锁 “- waiting to lock <0x00000007956143c0>”

从图上我们看到Thread0获取到了monitor锁,由于调用休眠方法,所以当前状态是TIMED_WAITING,而他们线程因为未能获取到monitor锁就直接就进入了BLOCKED状态,直到Thread0释放monitor锁之后才有资格去争抢monitor锁,如果再次未争抢到,依然是BLOCKED状态。

由此我们可以知道只要线程对同步操作的方法或语句未能争抢到monitor锁,就会进入BLOCKED状态,即使多个线程调用的是不同的方法,只要方法使用的是同一个monitor锁,那么线程都需要等候monitor锁的释放。

那么我们可以猜想一个问题,在同一个类中synchronized关键字作用在多个实例方法或者静态方法中,由不同的线程调用会出现上面堵塞的情况吗?

分别执行下面代码

我创建2条线程分别去调用sync1和sync2,从控制台打印的信息可以看出只执行了sync1,而sync2进入了BLOCKED状态,无论实例方法还是静态方法都是同样的结果,验证了我们的猜想。

如果线程分别调用的是实例方法和静态方法呢?结果回怎么样?大家可以去试一下。

JVM指令分析

jvm通过monitor enter 和monitor exit两个指令对同步操作进行了显示支持,执行monitor enter成功之前会从主内存中获取到最新的数据,而不是线程的线程内存,在执行monitor exit指令成功之后会释放monitor锁并且将共享变量的值从线程内存刷入到主内存中。保证了变量的内存一致性,这样就能保证操作的原子性

每个同步操作都会有一个monitor enter,一个或多个monitor exit出现。

通过上面的汇编代码(getstatic:获取类的静态属性,astore_n:将数据刷入到主内容,aload_n:从主内存中加载数据)。第5行代码执行了monitor enter指令获取到monitor锁,然后线程执行了sleep代码进入了休眠状态,结束休眠状态后执行monito exit指令释放monitor锁并且goto到31行执行return操作退出方法,

相关文章

网友评论

      本文标题:我对synchronized的一点理解

      本文链接:https://www.haomeiwen.com/subject/fijcfqtx.html