Java并发编程之synchronized关键字解析

前言

        公司加班太狠了,都没啥时间充电,这周终于结束了。这次整理了Java并发编程里面的synchronized关键字,又称为隐式锁,与JUC包中的Lock显示锁相对应;这个关键字从Java诞生开始就有,称之为重量级锁,自从JDK1.6之后官方对该关键字进行优化,引入了轻量级锁和偏向锁,于是就有了锁升级的概念。

使用

在代码中使用这个关键字总共有以下三种:

    private static Object object = new Object();
    private synchronized void function1() {
        //锁住当前实例对象
    }
    private static synchronized void function2() {
        //锁住当前class对象,可以认为是锁住当前Class文件
    }
    public static void main( String[] args ) {
        synchronized (object) {//锁住object对象

        }
    }

1:普通方法同步;

2:静态方法同步;

3:同步代码块括号中的对象;

使用synchronized关键字进行同步,则锁是储存于Java对象头里面的Mark Word。

Java对象头里面的Mark Word里面主要是存储对象的hashCode、分代年龄(用于判断为老年代还是年轻代,在垃圾回收器里面用得到)以及锁标记位。

锁的升级

在JDK1.6之后,引入了引入了偏向锁和轻量级锁的状态,目的是为了提升锁的释放和获取效率,减少性能的开销,所以synchronized就有四种级别锁的状态,级别从低到高分别是无锁状态、偏向锁状态、轻量级锁以及重量级锁状态;四种状态实质上是对象头储存的锁标记位不一致,使用CAS更改对象头的标记位进行锁状态位;并且在记录锁的标记位的同时,也会在Mark Word里面记录锁线程的ID

无锁

既在对象头的Mark Word没有标记锁状态的时候就是无锁状态

偏向锁

单个线程进行访问或调用带synchronized的同步代码块或方法时,会在先判断对象头里面锁标志位是否有线程ID,如果没有线程ID的话,将当前线程ID写入进去,并且也会在栈帧中的锁记录里面进行记录。此时,锁的状态位为偏向锁,通俗来说可以说是只要单线程访问同步代码块,从无锁状态就会便成为偏向锁状态;如果在对象头里面存在线程ID的话,如果当前线程ID是与对象头里面记录的线程ID的话,那么就可以直接访问,不需要使用CAS去进行竞争锁了;不一致的话,那么就会使用CAS进行竞争锁。

当其他线程开始竞争偏向锁的时候,那么持有偏向锁的线程就去会释放偏向锁,供其他线程使用;这时候就出现一种偏向锁的撤销概念

偏向锁的撤销

偏向锁的撤销就是,会先暂停持有偏向锁的线程,然后去对象头里面判断记录线程ID的线程是否还在处于活动状态,如果处于非活动状态,那么就会将Mark Word里面的锁标志位设置为无锁状态;如果处于活动状态的话,会先遍历偏向对象的锁记录、栈里面锁记录以及对象头里面Mark Word里面的锁标记位,并且将对象头中锁标志位设置为无锁状态或者升级成轻量锁状态的,然后唤醒持有偏向锁的的线程,继续执行;

轻量级锁

加锁

当执行同步代码块升级为轻量级锁的时候,会在栈帧中创建一块用于储存锁记录的空间,并且将对象头里面Mark Word复制到记录中(Displaced Mark Word),线程开始会使用CAS将Mark Word替换为指向锁记录的指针,如果获取成功,那么当前线程获取锁,如果失败,采用自旋来获取锁,如果自旋获取锁失败,那么会膨胀为重量级锁。

解锁

轻量级锁解锁会使用CAS将Displaced Mark Word替换回到对象头中,如果成功了,表示没有锁竞争;如果失败了表示有锁在竞争,那么就会膨胀成重量级锁,那么在自旋的获取锁的线程就会进行线程阻塞;

由于自旋会大量消耗CPU资源,所以一旦升级成为了重量级锁之后,那么就不会进行降级了。

重量级锁

这个就是线程阻塞了,基本上可以和Lock表现一致了,一旦有线程获取锁,其他获取锁的线程将会阻塞,释放锁之后将会唤醒阻塞线程去竞争锁

优点缺点使用场景
偏向锁加锁和解锁不需要额外的资源消耗,性能快如果线程之间存在锁竞争,那么会出现锁撤销,暂停线程,比较消耗资源适用于单线程使用场景
轻量级锁线程不会阻塞,提高响应程度自旋消耗CPU性能,容易升级为重量级锁适用于同步代码块执行非常快的
重量级锁线程不会自旋,避免过多消耗CPU资源线程阻塞,响应时间缓慢提高吞吐量,同步代码块执行较长

等待/通知机制

这个之前是有篇讲过线程之间的共享协作:线程协作 这个里面提到过如何使用

现在看看底层是如何运行的执行,我们先看一段代码

public class SynchronziedDemo {

	public static Object object = new Object();
	
	public static void main(String[] args) {
		synchronized (object) {
		}
		m();
	}
	
	public static synchronized void m() {
		
	}
}

将这段代码编译后使用javap -v命令进行反编译

会得到class文件的编译后的c代码:

同步代码块使用的事monitorenter(获取锁)和monitorexit(释放锁)指令,同步放上是使用ACC_SYNCRONIZED来完成的。

无论是哪个本质上其实是个monitor监视器进行完成的,每个对象都会有一个监视器,线程需先获取到monitor监视器才能访问同步代码块或者方法,而没有获取到的线程就会进入自旋,升级为重量级锁,然后会进入到阻塞状态,这时候会有一个同步队列(SynchronizedQueue),阻塞的线程会加入到这个队列里面,等待获取到监视器的线程调用monitorexit指定后,会唤醒同步队列里面的等待线程。

等待/通知机制里面对了一个WaitQueue即等待队列,案例:


public class WaitNotify {

	public static Object object = new Object();
	
	public static void main(String[] args) {
		new Thread(new WaitClass()).start();
		new Thread(new NotifyClass()).start();
	}
	
	static class NotifyClass implements Runnable {
		@Override
		public void run() {
			synchronized (object) {
				try {
					TimeUnit.SECONDS.sleep(2L);
					System.out.println(Thread.currentThread() + "notify start...");
					object.notifyAll();
					System.out.println(Thread.currentThread() + "notify end...");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	static class WaitClass implements Runnable {
		@Override
		public void run() {
			synchronized (object) {
				System.out.println(Thread.currentThread() + "wait start....");
				try {
					object.wait();
					System.out.println(Thread.currentThread() + "wait end....");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

 打印日志如下:

Thread[Thread-0,5,main]wait start....
Thread[Thread-1,5,main]notify start...
Thread[Thread-1,5,main]notify end...
Thread[Thread-0,5,main]wait end....

对于wait线程获取到object对象的监视器之后,调用wait方法后进入等待队列(WaitQueue),然后释放监视器。

对于notify线程来说,先获取到object对象监视器之后,然后调用notifyAll方法,将WaitQueue里面的所有等待线程同步到同步队列中,(如果是调用notify方法就会值同步一个线程并非所有线程),然后释放监视器,就会唤醒同步列队中的线程。

对于wait线程,在同步队列呗唤醒后,会重新获取监视器,然后继续执行wait方法后面的代码。

这样就完成一个等待通知的机制了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值