线程的同步机制(Synchronized锁)
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制 synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放即可.存在以下问题
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题
解决安全性
由于我们可以通过private
关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized
关键字,它包括两种用法:
synchronized方法和synchronized块
同步方法:public synchronized void method(int args){}
synchronized 方法控制对"对象"的访问,每个对象对应一把锁,每个 synchronized 方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
缺陷:若将一个大的方法申明为 synchronized 将会影响效率
同步监视器的执行过程
(1)第一个线程访问,锁定同步监视器,执行其中代码
(2)第二个线程访问,发现同步监视器被锁定,无法访问
(3)第一个线程访问完毕,解锁同步监视器
(4)第二个线程访问,发现同步监视器没有锁,然后锁定并访问
(5)继续执行以上操作
历史
在JDK5之前,Java中的锁是通过关键字synchronized来实现的,而在JDK5之后Lock接口的出现使得锁的使用更加的灵活。同时在JDK6开始对synchronized关键字进行了锁升级的优化,使其能够适用于更多的场景,不再是严格意义上的重量级锁
定义
synchronized是 JVM 中实现的一种锁,同时也是内置的java关键字,说明我们无法判断获取锁的状态。锁的获取和释放方式分别是monitorenter和moniterexit的一个指令,会自动释放锁,是一种可重入锁,不可以中断的、非公平。在线上分为了偏向锁、轻量级锁以及重量级锁,其中偏向锁在1.6是默认开启的,轻量级锁在多线程竞争情况下会膨胀成重量级锁,也就是说有关于这个锁的数据都会保存在对象头中。适合锁少量的代码同步问题,是一把悲观锁
- 偏向锁
偏向锁指的是当锁不存在多个线程竞争,并且经常由一个线程获取锁对象时,为了让线程获取锁的代价更低,在锁对象的mark word中保存了当前获取锁对象线程的线程ID,下次该线程要获取锁的时候可以不使用CAS的方式获取锁,而是直接通过比较线程ID来再次获取锁对象
- 轻量级锁
轻量级锁状态发生在当有多个线程访问同步代码块,但是访问时间是错开的,彼此之间没有竞争的时候。(注意偏向锁是发生在同一个线程反复访问同步代码块的时候)
轻量级锁状态的记录不再跟偏向锁一样在对象的mark word域中记录thread id,而是使用了线程栈中的栈帧的锁记录结构来记录轻量级锁状态
- 重量级锁
重量级锁和一个Monitor对象有关。每个Java对象都可以关联一个Monitor对象,如果使用synchronized关键字给对象加上重量级锁的时候,锁对象的mark word域就会指向Monitor对象
可重入锁:在自己以获取一把锁的状态下,⾃⼰是可以重复获取此锁的
可重入锁又称递归锁,是指同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提是锁对象得是同一个对象),不会因为之前已经获取过锁还没有释放而阻塞