JAVA锁相关
2020-01-12 本文已影响0人
依弗布德甘
Java锁的概念
-
自旋锁
循环抢锁,是指当一个线程在获取锁的时候,如果锁已经被其它线程抢占,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环 -
乐观锁
读取的时候不加锁,假定没有冲突,在修改数据时如果发现数据和之前获取的数据不一致,则退出或重新读取最新数据重试修改 -
悲观锁
从读取数据的时候就加锁,假设发生并发冲突,抢到锁后,才会同步所有对数据的相关操作 -
独享锁(写)互斥
同时只能有一个线程获得锁,一个线程获取到锁后,其他线程将会阻塞不会抢到锁。比如 ReentrantLock 是互斥锁 -
共享锁(读)
可以有多个线程同时获得锁,类似令牌池机制,可以定义同一时间能有多少线程抢到锁 -
可重入锁、不可重入锁
可重入锁:可多次加锁,不会阻塞。JDK提供的synchronized ,ReentrantLock都是可重入锁
不可重入锁:第二次加锁后会阻塞线程
-
公平锁、非公平锁
公平锁:抢锁的顺序与抢到锁的顺序一致,会排队
非公平锁:抢锁的顺序与抢到锁的顺序不一致
自己实现锁-不可重入锁
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
public class DemoLock implements Lock {
//锁的拥有者
AtomicReference<Thread> owner = new AtomicReference<>();
//等待队列
private LinkedBlockingQueue<Thread> waiter = new LinkedBlockingQueue<>();
@Override
public boolean tryLock() {
return owner.compareAndSet(null, Thread.currentThread());
}
@Override
public void lock() {
if (!tryLock()){
// 抢锁不成功,加入等待队列
waiter.offer(Thread.currentThread());
// 用死循环防止为唤醒问题
while(true){
// 去除队列头部,但是不出队列
Thread head = waiter.peek();
// 判断是队列头部
if (head == Thread.currentThread()){
// 抢锁
if(!tryLock()){
//失败,挂起线程
LockSupport.park();
}else{
//抢锁成功,将线程出度列
waiter.poll();
return;
}
}else{
//不是头部,线程挂起
LockSupport.park();
}
}
}
}
@Override
public void unlock() {
if (tryUnlock()){
Thread th = waiter.peek();
if (th !=null){
LockSupport.unpark(th);
}
}
}
public boolean tryUnlock(){
//首先判断当前线程是否站有锁
if (owner.get() !=Thread.currentThread()){
throw new IllegalMonitorStateException();
}else{
return owner.compareAndSet(Thread.currentThread(), null);
}
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public Condition newCondition() {
return null;
}
}
可重入锁和不可重入锁的区别
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantTest {
private static int i = 0;
private final static Lock lc1 = new ReentrantLock(); //可重入锁
private final static Lock lc2 = new DemoLock(); //不可重入锁
public static void recursive() throws InterruptedException {
lc1.lock();
i++;
System.out.println("here i am...");
Thread.sleep(1000L);
// 休眠一秒后重新执行该方法,lock不影响
recursive();
lc1.unlock();
}
public static void main(String args[]) throws InterruptedException {
lc2.lock();
System.out.println("加锁第一次。。。");
lc2.lock();
// 第二次加锁后,线程阻塞,不执行
System.out.println("加锁第二次。。。");
lc.unlock();
lc.unlock();
//recursive();
}
}
同步关键字synchronized
synchronized关键字通过修饰一个方法或声明一个代码块,从而产生一个同步对象锁以及对应的同步代码块,是通过锁对象的Monitor的取用与释放来修改其对象的头部信息,实现加锁解锁
- Monitor(对象监视器)是内置于任何一个对象中
// ObjectMonitor底层结构 C++代码
ObjectMonitor::ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL;
_WaitSet = NULL;
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ;
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
-
synchronized 它的同步包括:
- 对于普通方法同步,锁是当前实例对象
- 对于静态方法同步,锁是当前类的 Class 对象
- 对于方法块同步,锁是 Synchronized 括号里的对象
-
特性:可重入锁,独享锁,悲观锁
-
synchronized的使用
import com.study.lock.source.bak.ReentrantLock;
import java.util.concurrent.locks.Lock;
public class Demo {
public static void main(String args[]){
//如果你在实例方法上加锁,
//多个线程,抢的是同一个所,还是多个锁
//Counter ct1 = new Counter();
//Counter ct2 = new Counter();
new Thread(new Runnable() {
@Override
public void run() {
Counter.staticUpdate();
//ct1.update();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
Counter.staticUpdate();
//ct1.update();
}
}).start();
}
}
class Counter{
private static int i = 0;
public synchronized void update() {
//访问数据库
}
// 与update的写法语意一致
public void updateBlock(){
synchronized (this){
//访问数据库
}
}
public static synchronized void staticUpdate(){
//访问数据库
}
// 与staticUpdate的写法语意一致
public static void staticUpdateBlock(){
synchronized (Counter.class){
//访问数据库
}
}
Lock lock = new ReentrantLock();
public void updateReentrantLock(){
lock.lock();
try {
//访问数据库
}finally {
lock.unlock();
}
}
}
-
锁优化
锁消除: 在同一个线程里面,局部变量出现重复的加锁解锁,JIT编译之后,直接去掉了锁锁粗化: 在同一线程中,同一方法中出现重复加锁解锁,JIT编译会忽略多次加锁合并成一个锁
public void test(Object arg) {
// StringBuilder线程不安全,StringBuffer用了synchronized,是线程安全的
// 局部变量,没有在其他线程中使用
// 每次append都用了synchronized
// jit 优化, 消除了锁
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("a");
stringBuffer.append("b");
stringBuffer.append("c");
stringBuffer.append("a");
stringBuffer.append("b");
stringBuffer.append("c");
System.out.println(stringBuffer.toString());
}
volatile int i = 0;
public void test(Object arg) {
// 每次加锁都对 i 操作
// jit 优化, 合并锁
synchronized (this){
i++;
i--;
//生成随机数
}
synchronized (this){
//生成随机数
i--;
}
}
synchronized 底层原理
- 代码解析
public class Demo {
public static void main(String args[]){
int a = 1;
Teacher teacher = new Teacher();
teacher.stu = new Student();
}
}
class Teacher{
String name = "james";
int age = 40;
boolean gender = true;
Student stu;
public void shout(){
}
}
class Student{
String name = "Emily";
int age = 18;
boolean gender = false;
}
- 代码在内存中如何存储
![](https://img.haomeiwen.com/i5888874/82a3be9690c88047.png)
- synchronized 加锁,其操作的就是 对象头部的 MarkWord
![](https://img.haomeiwen.com/i5888874/034b4fa5229be132.png)
- 两个线程抢锁之前,会先读取MarkWord的值
- 两个线程抢锁之前,会先读取MarkWord的值
- CAS操作会抢锁,只会有一个线程抢到锁
- 线程1抢到锁后,对象头部成为轻量级锁
- 线程2未抢到锁,将自旋
- 自旋一定程度,锁升级。CAS操作将把锁改为重量级锁
-
重量级锁
- 锁的升级过程:一旦对象锁升级为重级锁后,后续线程对该对象的操作都将是加锁为重量级锁
- 锁不存在降级:锁只有从偏向锁->轻量级/重量级 或者 直接 轻量级-> 重量级. 不会降级,只有解锁
![](https://img.haomeiwen.com/i5888874/de79c868c2dd55ce.png)
- 线程1对当前对象抢锁成功后,对象onwer等于线程1。线程2自旋
- 自旋到一定程度,锁升级,线程2加入锁池,并持续抢锁
- 线程1调用wait()方法后,线程1释放锁,并加入WaitSet等待池中。onwer等于空
- 线程2抢锁成功,onwer等于线程2,线程2出entryList(锁池),执行线程2
- 线程2执行完毕,线程1.notify()方法,线程1退出等待池,onwer等于线程1
- 如果此时线程2又来抢锁,线程2加入锁池
wait、notify和notifyAll方法是Object类的final native方法,这些方法不能被子类重写;
wait只能在同步方法中调用,notify和notifyAll只能在同步方法或同步块内部调用;
-
偏向锁
偏向锁是指,程序在编译过程中,只发现只有一个地方使用到(一个线程),会对该对象一直处于偏向锁的状态中,从而提高性能。一旦有其他线程来对该对象抢锁后,则锁升级
如果需要,使用参数-XX:-UseBiasedLocking禁止偏向锁优化(默认打开)