线程安全是并发编程中的重要关注点,造成线程安全问题的主要原因有两点,一是存在共享数据(也称临界资源),二是存在多条线程共同操作共享数据。
因此为了解决这个问题,我们可能需要这样一个方案,当存在多个线程操作共享数据时,需要保证同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再进行,这种方式叫互斥锁,即能达到互斥访问目的的锁,也就是说当一个共享数据被当前正在访问的线程加上互斥锁后,在同一个时刻,其他线程只能处于等待的状态,直到当前线程处理完毕释放该锁。
一、synchronized锁机制
1、synchronized 的作用:
synchronized 通过当前线程持有对象锁,从而拥有访问权限,而其他没有持有当前对象锁的线程无法拥有访问权限,保证在同一时刻,只有一个线程可以执行某个方法或者某个代码块,从而保证线程安全。
synchronized 可以保证线程的可见性,synchronized 属于隐式锁,锁的持有与释放都是隐式的,我们无需干预。synchronized最主要的三种应用方式:
-
修饰实例方法:作用于当前实例加锁,进入同步代码前要获得当前实例的锁
-
修饰静态方法:作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
-
修饰代码块:指定加锁对象,进入同步代码库前要获得给定对象的锁
2、synchronized 底层语义原理:
synchronized 锁机制在 Java 虚拟机中的同步是基于进入和退出监视器锁对象 monitor
实现的(无论是显示同步还是隐式同步都是如此),每个对象的对象头都关联着一个 monitor
对象,当一个 monitor
被某个线程持有后,它便处于锁定状态。在 HotSpot
虚拟机中,monitor 是由 ObjectMonitor
实现的,每个等待锁的线程都会被封装成 ObjectWaiter
对象,ObjectMonitor
中有两个集合,_WaitSet
和 _EntryList
,用来保存 ObjectWaiter
对象列表 ,owner 区域指向持有 ObjectMonitor
对象的线程。
当多个线程同时访问一段同步代码时,首先会进入 _EntryList
集合尝试获取 moniter,当线程获取到对象的 monitor
后进入 _Owner
区域并把 _owner
变量设置为当前线程,同时 monitor
中的计数器 count 加1;若线程调用 wait()
方法,将释放当前持有的 monitor
,count自减1,owner 变量恢复为 null,同时该线程进入 _WaitSet
集合中等待被唤醒。若当前线程执行完毕也将释放 monitor
并复位变量的值,以便其他线程获取 monitor
。如下图所示:
-
_EntryList
:存储处于Blocked
状态的ObjectWaiter
对象列表。 -
_WaitSet
:存储处于wait
状态的ObjectWaiter
对象列表。