Java并发编程:Lock - Matrix海子 - 博客园
二.如何解决线程安全问题?
当多个线程同时对同一共享资源进行操作时,就会产生线程安全问题。为了保证线程安全,经常会使用synchronized和Lock锁。
采用“序列化访问临界资源”的方案,即在同一时刻,只能有一个线程访问临界资源,也称作同步互斥访问。
三.synchronized同步方法或者同步块
Synchronized的语义底层是通过一个monitor的对象来完成:Java中每个对象都有一把monitor锁。一个要执行对象的synchronized代码的线程必须先获得那个对象的锁。
四、Synchronized的基本使用
(1)修饰普通方法
(2)修饰静态方法
(3)修饰代码块
五、java对象
对齐填充:对象的起始地址必须为8字节的整数倍
六、Synchronized 原理
Synchronized代码块进行反编译:
参考JVM规范中描述:
每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,
monitorenter :
线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
2、如果该线程之前已经占有monitor,只是重新进入,则进入monitor的进入数加1.
3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
monitorexit:
执行monitorexit的线程必须是object所对应的monitor的所有者。
指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。
同步方法的反编译结果:
源代码:
1 package com.paddx.test.concurrent; 2 3 public class SynchronizedMethod { 4 public synchronized void method() { 5 System.out.println("Hello World!"); 6 } 7 }
反编译结果:
JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。
七、为什么优化synchronized
JDK1.6以后对sychronized进行了锁优化,在并发量不太大的情况下不用讲线程挂起,减少了线程挂起和唤醒这个上下文切换过程的过程开销。因为挂起和唤醒线程需要用户态到内核态的切换,切换时间长成本高。
无锁:不加synchronized关键字,表示无锁
偏向锁:将线程id记录到对象的markword中。比较Markword的线程id是否和当前线程id相等比较后不相等说明有其他线程竞争锁,synchronized会升级成轻量级锁
轻量级锁: 当存在第二个线程申请同一个锁对象时,偏向锁就会立即升级为轻量级锁。多个线程交替进入临界区
自旋锁:
重量级锁: 多个线程同时进入临界区,有两种情况会膨胀成重量级锁。1种情况是cas自旋10次还没获取锁。第2种情况其他线程正在cas获取锁,第三个线程竞争获取锁,锁也会膨胀变成重量级锁。
synchronized的执行过程:
1. 检测Mark Word里面是不是当前线程的ID,如果是,表示当前线程处于偏向锁
2. 如果不是,则使用CAS将当前线程的ID替换Mard Word,如果成功则表示当前线程获得偏向锁,置偏向标志位1
3. 如果失败,则说明发生竞争,撤销偏向锁,进而升级为轻量级锁。
4. 当前线程使用CAS将对象头的Mark Word替换为锁记录指针,如果成功,当前线程获得锁
5. 如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
6. 如果自旋成功则依然处于轻量级状态。
7. 如果自旋失败,则升级为重量级锁。没有获取到锁的线程阻塞挂起,直到持有锁的线程执行完同步块唤醒他们;
八、synchronized的特性:
原子性: 同一时间只有一个线程能拿到锁,进入代码块
注意!面试时经常会问比较synchronized和volatile,它们俩特性上最大的区别就在于原子性,volatile不具备原子性。
可见性:一个线程对变量的修改,其他线程获取锁之后可见。synchronized关键字包含了两个JVM指令:monitor enter和monitor exit,它能够保证在任何时候任何线程执行到monitor enter时都必须从主内存中获取数据,而不是从线程工作内存获取数据,在monitor exit之后,工作内存被更新后的值必须存入主内存,从而保证了数据可见性。
而volatile的实现类似,被volatile修饰的变量,每当值需要修改时都会立即更新主存,主存是共享的,所有线程可见,所以确保了其他线程读取到的变量永远是最新值,保证可见性。
可重入性:线程请求自己已持有的锁对象
不可中断:一个线程获取锁之后,只要他不释放,其他线程只能等待